MALDA™ Reference Manual

The AI-First Programming Language - Version 1.0.11

27. REST API Server

MALDA includes native support for creating REST API servers using function decorators.

27.1 Decorator Syntax

Functions can be decorated with HTTP method decorators to create REST endpoints:

@GET("/api/users")
function getUsers() {
    return parseJSON("[{\"id\": 1, \"name\": \"Alice\"}]");
}

@POST("/api/users")
function createUser(body) {
    var jsonStr = "{\"id\": 3, \"name\": \"" + body.name + "\"}";
    return parseJSON(jsonStr);
}

Supported HTTP Methods

Route Metadata Decorators

Routes can include optional metadata decorators. These are applied before route registration:

@RouteGroup("/api")
@Version("v1")
@Use("requireAuth")
@GET("/users/{id}")
function getUserById(id, req, res) {
    return res.json(parseJSON("{\"id\":\"" + id + "\"}"));
}

27.2 RestServer Class

Constructor

var server = new RestServer(port);
// OR
var server = new RestServer(port, host);  // host: "localhost" or "0.0.0.0"

port must be 0 (deferred/mounted) or an integer in 1-65535. Privileged ports (1-1023) are allowed; binding may still require elevated permissions or OS URL reservations.

Methods

27.3 Path Parameters

Path parameters are extracted from route patterns using {param} placeholders:

@GET("/api/users/{id}")
function getUserById(id) {
    var jsonStr = "{\"id\": \"" + id + "\", \"name\": \"Alice\"}";
    return parseJSON(jsonStr);
}

27.4 Query Parameters

Query parameters are automatically extracted from the query string:

@GET("/api/users")
function getUsers(limit, offset) {
    // 'limit' and 'offset' extracted from ?limit=10&offset=0
    var jsonStr = "{\"users\": [], \"limit\": " + limit + "}";
    return parseJSON(jsonStr);
}

27.5 Request Body

The request body is automatically parsed as JSON and bound to a parameter named body:

@POST("/api/users")
function createUser(body) {
    var name = body.name;
    var jsonStr = "{\"id\": 1, \"name\": \"" + name + "\"}";
    return parseJSON(jsonStr);
}

27.6 Parameter Binding

Optional decorator-based binding for renaming parameters:

@PUT("/api/users/{id}/posts/{postId}")
function updatePost(@PathParam("id") userId, @PathParam("postId") postId, 
                    @QueryParam("limit") maxResults, @Body() updateData) {
    // userId = path param {id}
    // postId = path param {postId}
    // maxResults = query param ?limit=10
    // updateData = request body
}

27.7 Response Handling

Functions can return objects with status property to set HTTP status codes:

@POST("/api/users")
function createUser(body) {
    var jsonStr = "{\"status\": 201, \"data\": {\"id\": 1, \"name\": \"" + body.name + "\"}}";
    return parseJSON(jsonStr);  // Returns 201 Created
}

Handlers can also accept a request/response context pair:

@GET("/api/users/{id}")
function getUser(req, res) {
    var id = req.params.id;
    return res.status(200).json(parseJSON("{\"id\": \"" + id + "\"}"));
}

27.8 Middleware and Request Context

Use server.use(...) for global middleware. Middleware receives three arguments: (req, res, next).

Route-level middleware declared via @Use(...) / @Middleware(...) runs after global middleware and before parameter binding + handler invocation.

function logRequest(req, res, next) {
    print(req.method + " " + req.path);
    next();
}

var server = new RestServer(8080);
server.use(logRequest);
server.start();

Auth guards are composable middleware. Prefer req.auth.authenticateBearerJwt(secret) (or authenticateCookieJwt for browser sessions): it verifies the JWT, populates claims/roles/permissions, and throws standardized 401 errors on failure.

function requireAuth(req, res, next) {
    req.auth.authenticateBearerJwt("my-jwt-secret");
    next();
}

var server = new RestServer(8080);
server.use(requireAuth, {
    "except": ["/api/health", "/api/readiness", "/metrics"]
});
server.start();

Skip public paths with use(middleware, { "except": [...] }). Exact paths and trailing * prefixes are supported. For ingress/gateway trust boundaries you can still call req.auth.setVerifiedSub(sub) after an upstream check; prefer authenticate* inside application middleware so roles and claims are available.

27.9 Response Helper API

Response context helper methods:

res.cookie(...) uses secure defaults: HttpOnly=true, Secure=true, SameSite=Lax, Path=/.

27.10 CSRF and Rate Limiting

Use built-in middleware-style server configuration for request protection without ad-hoc per-route logic:

var server = new RestServer(8080);
server.enableCsrf("my-csrf-secret");                 // cookie: csrf_token, header: X-CSRF-Token
server.setRateLimit(120, 60, "verifiedSubOrIp");     // after auth middleware; prefer subject when verified
server.setRateLimitHeaders(true, true);              // add X-RateLimit-* + Retry-After headers
server.configureTrustedProxy(true, "X-Forwarded-For", 0); // trust first forwarded hop
server.use(requireAuth, { "except": ["/api/health", "/metrics"] });
server.start();

Rate-limit key strategy supports ip, token, user, sub/verifiedSub/verifiedSubOrIp, and ipOrToken (default fallback).

Rate limiting runs after global and route middleware, so verifiedSub* strategies see subjects set by req.auth.authenticateBearerJwt / authenticateCookieJwt. When no verified subject is present, keying falls back to IP/token behavior.

Header aliases (X-Malda-Auth-Verified/X-Malda-Auth-Sub and legacy X-Auth-*) remain available for compatibility at ingress boundaries, but application middleware should use req.auth.

Trusted proxy defaults are secure: proxy headers are ignored unless explicitly enabled via configureTrustedProxy(...).

When setRateLimitHeaders(true) is enabled, responses include Retry-After (on 429), X-RateLimit-Limit, and optionally X-RateLimit-Remaining.

CSRF behavior:

CSRF, auth, and rate-limit failures all use the same standardized error payload and include correlation ID headers/payload fields.

27.11 Error Handling

Functions can throw objects with status property to return custom HTTP status codes:

@GET("/api/users/{id}")
function getUserById(id) {
    if (!userExists(id)) {
        var error = parseJSON("{\"status\": 404, \"message\": \"User not found\"}");
        throw error;
    }
    // ...
}

REST framework errors (handler failures, middleware/auth failures, and validation failures) use one JSON contract:

{
  "status": 401,
  "error": "InvalidToken",
  "message": "Invalid token signature.",
  "correlationId": "...",
  "details": [ ... ] // optional, present for validation errors
}

Correlation ID propagation is consistent across all framework-generated failures:

27.12 Validation

Use @Validate(...) to validate path, query, and body before your handler runs. Invalid requests return HTTP 400 with field-level details and do not execute handler logic.

@GET("/search/{id}")
@Validate("{\"path\":{\"id\":\"int|required|min=1\"},\"query\":{\"q\":\"string|required|minLength=2\"}}")
function search(id, q) {
    return parseJSON("{\"ok\": true}");
}

Schema values support string DSL rules such as required, min=, max=, minLength=, maxLength=, and pattern=.

When Swagger is enabled, validation metadata is reflected in OpenAPI parameter/request-body schemas (type, required, and common constraints).

27.13 CORS Support

var server = new RestServer(8080);
server.enableCORS(true);
server.setCORSOrigin("*");
server.start();

27.14 Swagger/OpenAPI Documentation

var server = new RestServer(8080);
server.enableSwagger(true);
server.start();

// Access at: http://localhost:8080/swagger.json

27.15 Complete Example

function json(str) {
    return parseJSON(str);
}

@GET("/api/health")
function healthCheck() {
    return json("{\"status\": \"healthy\"}");
}

@GET("/api/users/{id}")
function getUserById(id) {
    var jsonStr = "{\"id\": \"" + id + "\", \"name\": \"Alice\"}";
    return json(jsonStr);
}

@POST("/api/users")
function createUser(body) {
    var jsonStr = "{\"status\": 201, \"data\": {\"id\": 3, \"name\": \"" + body.name + "\"}}";
    return json(jsonStr);
}

var server = new RestServer(8080);
server.start();

while (server.isRunning) {
    sleep(1000);
}

See Also