MALDA™ Reference Manual

The AI-First Programming Language - Version 1.0.39

29. REST API Server

MALDA includes native support for JSON-first REST APIs. Decorate functions with HTTP methods, then host them on RestServer (standalone) or mount that server on HttpServer for a single-port full-stack app.

RestServer, HttpServer, and sessions run on the interpreter and C# transpile. They are not available on the JavaScript backend — keep the server on the host and call it from the browser (30. REST Web Client). See the backend capability matrix. Interpret vs transpile handler notes are in 29.14.

Start here: New to web APIs? Read 29.1.1 Concepts primer first — REST, middleware, auth, JWT, CSRF, CORS, HTTPS, and a threat-to-defense map, without leaving this manual. This chapter is the API-first reference. Page hosting, @PAGE, HTTPS, static files, multipart uploads, and SSE live in 27. HttpServer & HTML UI Generation. Calling APIs from MALDA is 30. REST Web Client. JWT, CSRF, and password helpers are in 29.16 (name index: 13.25).
Why this page is not a lesson: A real example listens on a port. That cannot run in the manual snippet harness. Smallest file: Examples/Web/rest_api_server.malda. Start from 25. Web UI Overview. Course: Learn MALDA — After the core path.

29.1 Chapter Map

29.1.1 Concepts primer

The later sections assume a few web terms. This primer is enough to follow the MALDA APIs without an external glossary; it is not a full HTTP textbook. Skim it once, then jump to the section that names the API you need. A one-page threat-to-defense map sits at the end.

REST and HTTP

HTTP is the request/response protocol the browser and your server already speak. A client sends a method (GET, POST, PUT, PATCH, DELETE, OPTIONS) plus a path, optional headers, and sometimes a body. The server answers with a status code plus headers and a body.

REST here means: expose operations as HTTP routes that exchange JSON. @GET("/api/users/{id}") is a read; @POST("/api/users") creates; PUT/PATCH update; DELETE removes.

Piece of the requestExampleWhere MALDA puts it
Method + pathGET /api/users/12@GET("/api/users/{id}"); id is a path parameter
Query string?status=openHandler argument or req.query
HeadersAuthorization: Bearer …req.headers, or req.auth after JWT middleware
JSON body{"name": "Ada"}Handler body argument or req.body

Status families: 2xx success, 4xx the client sent something the server will not accept, 5xx the server failed. Codes you will see often: 200 OK, 400 validation failed, 401 not authenticated, 403 authenticated but not allowed, 404 no such route or resource, 429 rate limited, 500 uncaught server error. MALDA's JSON error envelope is 29.11.

GET and PUT are usually idempotent (repeating them does not create a second resource). POST often is not. That matters for retries — see 30.1.1.

Middleware

A route handler should do the business work (load a user, save a row). Middleware is a function that runs before that handler, for work that many routes share: logging, authentication, attaching a tenant id. Think of a conveyor: every request passes through the same gates, then the specific handler.

The MALDA contract is (req, res, next). Call next() to continue the chain; skip next() (or send a response) to stop — that is how you reject an unauthenticated caller without running the handler. Global middleware is server.use(...); per-route is @Use("name"). Details: 29.8. Pipeline order: 29.4.

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

function requireAuth(req, res, next) {
    req.auth.authenticateBearerJwt("replace-me");
    next();
}

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

Request flow, left to right: CSRF (if enabled) → global middleware → route @Use → rate limit → @Validate → handler. Auth middleware must run before the rate limiter if you want to key limits on the verified user.

Authentication vs authorization

Authentication answers “who is this?”. Authorization answers “may they do this?”. Login proves identity; a role or permission check decides access. In MALDA, req.auth.authenticateBearerJwt(secret) authenticates; req.auth.requireRole("admin") authorizes. A missing or bad token is HTTP 401; a known user lacking a role is 403. Never treat “the request has an X-User-Id header” as authentication — anyone can send that header. See 29.6.2.

JWT and Bearer tokens

A JWT (JSON Web Token) is a signed string: a JSON payload (claims such as sub, role, expiry) plus a signature the server verifies with a secret. The client usually sends it as Authorization: Bearer <token>.

Three words that are easy to mix up: encoding (Base64, reversible, not a secret), hashing (one-way, for passwords — next heading), signing (proves the token was minted with your secret; MALDA JWTs are signed, not encrypted). Anyone who has the JWT can read the claims, so do not put passwords in them, treat the token like a password, keep the signing secret off the client, and set a short expiry.

Create tokens with createJwt (29.16); verify them in middleware with authenticateBearerJwt. Cookie-based JWTs use authenticateCookieJwt instead of the Authorization header.

Password hashing

Never store a login password as plaintext. A password hash is a one-way value: given the hash you cannot recover the password, but verifyPassword can check a later login against it. MALDA uses salted PBKDF2 (hashPassword / verifyPassword in 29.16). A salt is random data mixed in so two users with the same password do not get the same hash. This is not JWT signing and not HTTPS encryption — it is only for credentials at rest.

Sessions and cookies

A cookie is a small name/value the server sets on the response; the browser sends it back on later requests to the same site. Flags that matter:

A session is server-side storage keyed by a session-id cookie — req.session after enableSession. Prefer sessions or cookie JWTs for browser apps (the token is not sitting in JavaScript). Prefer Bearer JWTs for programmatic API clients (scripts, other servers, mobile). Cookie defaults: 29.7.2. Session API: 29.6.3.

CSRF

CSRF (Cross-Site Request Forgery) is a trick where another site causes the user's browser to send a request to yours with the user's cookies. The user is logged in, so the request looks legitimate — for example a hidden form on evil.example that POSTs to https://your-app/api/transfer.

Defense: the real page includes a secret token (header or hidden form field) that the attacker site cannot read (the browser's same-origin policy blocks that). Safe methods (GET/HEAD/OPTIONS) may mint the cookie; POST/PUT/PATCH/DELETE must present a matching token. Bearer JWT APIs called from non-browser clients do not send cookies, so CSRF usually does not apply to them; it does apply to cookie sessions and cookie JWTs. Enable with enableCsrf. Details: 29.9.

CORS

Browsers isolate pages by origin (scheme + host + port). https://app.example and https://api.example are different origins; so are http://localhost:5173 and http://localhost:8080. A page cannot read a response from another origin unless that API opts in with CORS headers. The browser may first send an OPTIONS preflight asking “may I call you this way?”.

CORS is not authentication and not CSRF. Same-origin calls (the MALDA UI and API on one HttpServer port) do not need it. Host-side RestClient is not a browser, so CORS does not apply to it. Enable CORS only if browsers on another origin will call you. Details: 29.12 and 30.9.

XSS

XSS (Cross-Site Scripting) is injecting script into a page the victim's browser will run — for example concatenating a username into HTML without escaping. The injected script can then read non-HttpOnly cookies or call your API as that user. MALDA does not auto-escape strings you put in HTML: treat every untrusted value as data, not markup, especially in @PAGE handlers (chapter 27). HttpOnly cookies reduce the blast radius; they do not fix XSS.

Rate limiting and trusted proxies

Rate limiting caps how many requests a key (IP, user, token) may make in a window, so a buggy client or attacker cannot overwhelm the server or brute-force a login. MALDA's limiter runs after middleware so it can key on a verified JWT subject.

A reverse proxy (nginx, Caddy, a cloud load balancer) sits in front of your app, often terminating TLS. The TCP peer your process sees is then the proxy, not the user. X-Forwarded-For carries the client IP — but anyone can spoof that header unless you only trust it from your proxy (configureTrustedProxy). Details: 29.9.

HTTPS / TLS

HTTPS is HTTP over TLS: the connection is encrypted and the server presents a certificate so clients know they reached the right host. Without it, cookies, passwords, and Bearer tokens travel in the clear on the network. Enable TLS on HttpServer with enableHttps (chapter 27). Standalone RestServer does not terminate TLS; mount it on HttpServer or put a reverse proxy in front. Keep signing secrets and certificate passwords in environment variables, not in source.

Correlation IDs, validation, and OpenAPI

A correlation ID is a string that tags one request across logs and error payloads so you can find “what happened to this call”. MALDA honors inbound X-Correlation-ID or generates one (29.11).

Validation checks that path, query, and body match a schema before your handler runs, so bad input fails with 400 instead of crashing later. That is @Validate (29.10) — related to, but not the same as, prompt/LLM validate() in chapter 10.

OpenAPI (often called Swagger) is a machine-readable description of your routes, parameters, and schemas. MALDA can serve it as JSON when you call enableSwagger(true) (29.12). Humans and code generators read it; it is not a security control.

Which defense for which threat

If you worry about…ConceptMALDA switch
A caller pretending to be a userAuthenticationauthenticateBearerJwt / cookie JWT / session
A logged-in user doing admin workAuthorizationrequireRole / requirePermission
Passwords stolen from your databasePassword hashinghashPassword / verifyPassword
Another site submitting a form as the userCSRFenableCsrf (cookie sessions / cookie JWTs)
A page on another origin calling your APICORSenableCORS only if you want that
Script injected into your HTMLXSSDo not concatenate untrusted strings into HTML
Tokens or cookies sniffed on the wireHTTPSenableHttps or a TLS reverse proxy
Floods or login brute-forceRate limitingsetRateLimit
Wrong client IP behind nginxTrusted proxyconfigureTrustedProxy

29.2 Decorator Syntax

Functions decorated with an HTTP method become REST endpoints when a RestServer starts (or is mounted):

@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);
}

29.2.1 Supported HTTP Methods

Duplicate method+path pairs throw at startup. Use getRoutes() to inspect the registered table.

29.2.2 Route Metadata Decorators

Optional metadata decorators are applied before the route is registered. Prefixes compose as group + version + path.

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

29.3 RestServer Class

29.3.1 Constructor

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

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. Host defaults to localhost. Pass 0.0.0.0 to listen on all interfaces. Unlike HttpServer, there is no setHost method and no MALDA_HTTP_HOST override.

29.3.2 Properties

PropertyTypeNotes
portintConfigured port; 0 when deferred/mounted
isRunningboolStandalone listener, or mirrors the host after mount

29.3.3 Methods

MethodNotes
start()Scan decorated functions, reject route conflicts, print the route table, bind HttpListener. Mounted: no listener; isRunning follows the host
stop()Stop the standalone listener
getRoutes()Array of {method, path}
use(middleware, options?)Global middleware: function, or function-name string in transpile. options.except skips exact paths or trailing-* prefixes
enableCORS(enabled) / setCORSOrigin(origin) / setCORSMethods(array) / setCORSHeaders(array)CORS. Defaults: origin *, methods GET/POST/PUT/DELETE/PATCH/OPTIONS, headers Content-Type and Authorization
enableSwagger(enabled)Serve OpenAPI JSON (see 29.12)
enableCsrf(secret, cookieName?, headerName?) / disableCsrf()Built-in CSRF. Defaults: cookie csrf_token, header X-CSRF-Token
enableSession(secret, options?) / disableSession()Signed session cookie and req.session. Options: cookieName (default malda_session), maxAge seconds (default 14 days), store "memory" or "sqlite", sqlitePath
setRateLimit(limit, windowSeconds, keyStrategy?) / disableRateLimit()Per-key limit. Strategies: ip, token, user (X-User-Id), sub / verifiedSub / verifiedSubOrIp, ipOrToken (default)
setRateLimitHeaders(enabled, includeRemaining?)X-RateLimit-Limit, optional X-RateLimit-Remaining, and Retry-After on 429
configureTrustedProxy(enabled, headerName?, hopIndex?)Off by default. When enabled, read client IP from X-Forwarded-For (or headerName) at hopIndex (default 0)

29.3.4 What RestServer does not host

Standalone RestServer is API-only. These belong on HttpServer (chapter 27): HTTPS / enableHttps, setHost / MALDA_HTTP_HOST, static files, @PAGE / @AIPAGE, multipart/form-data uploads, and SSE ({"sse": true}). The shared pieces are req / res / req.auth / req.session, CSRF, rate limits, and route metadata.

29.4 Request Pipeline

Every request runs this order. Knowing it matters for auth keys, CSRF, and validation:

  1. Resolve correlation ID: honor inbound X-Correlation-ID or generate one; echo it on the response
  2. CORS preflight (OPTIONS) when CORS is enabled, then CORS headers on the real request
  3. Swagger static paths when enabled
  4. Route match; no match → 404 standard envelope
  5. Parse body for POST / PUT / PATCH
  6. CSRF (before any application middleware)
  7. Global middleware (server.use)
  8. Route middleware (@Use / @Middleware)
  9. Rate limit (after middleware, so verifiedSub* sees req.auth)
  10. @Validate schema
  11. Parameter binding and handler
  12. Serialize or commit the response

Interpreted handlers run on an isolated interpreter per request. In interpret mode, assigning req.tenant = "acme" in middleware is visible to later middleware and the handler on that request only.

29.5 Parameter Binding

29.5.1 Path parameters

Placeholders in the route pattern bind by name:

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

29.5.2 Query parameters

Unmatched parameter names bind 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);
}

29.5.3 Request body

A parameter named body receives the parsed payload. Content types:

RestServer does not parse multipart/form-data. Use HttpServer for file uploads.

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

29.5.4 Special parameter names

These names always bind, in both name-based and decorator modes:

ParameterBinds to
req / requestRequest context
res / responseResponse context
params / pathParamsPath-parameter object
query / queryParamsQuery-parameter object
headersRequest headers
cookiesRequest cookies
bodyParsed body (name-based mode)

29.5.5 Decorator binding

Optional renaming. If any parameter has a decorator, undecorated parameters bind null (special names above still bind):

@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
}

In transpile mode the compiler registers routes with paramDecorators = null, so @PathParam / @QueryParam / @Body renaming works in the interpreter only. Prefer matching parameter names to path/query/body when you compile.

29.6 Request Context

29.6.1 req properties and helpers

MemberNotes
method, path, pathBase, url, queryStringRequest line
scheme / protocol, host, contentTypeConnection and content
query / queryParams, params / pathParams, headers, cookies, bodyParsed maps / payload
correlationIdInbound or generated; also sent as X-Correlation-ID
ip / remoteIpPeer IP; proxy headers ignored unless configureTrustedProxy is on
auth / authContextSee 29.6.2
sessionSee 29.6.3
verifiedSubVerified subject or null
header(name, default?), queryParam(name, default?), param(name, default?), cookie(name, default?)Lookups
hasHeader / hasQueryParam / hasParam / hasCookieBoolean presence
validate(schema)Returns {ok, errors, message?} without throwing
requireValid(schema)Throws a 400 ValidationError envelope

29.6.2 req.auth

Prefer req.auth.authenticateBearerJwt(secret) (APIs) or authenticateCookieJwt(cookieName, jwtSecret, cookieSecret?) (browser sessions). Both verify the JWT, populate claims/roles/permissions, and throw a standardized 401 on failure. Create tokens with createJwt (29.16).

MemberNotes
verifiedTrue after a successful authenticate / setVerifiedSub
sub / subjectVerified subject, or null
claimsJWT payload object
roles, permissionsFrom role/roles and permission/permissions/scope/scp
token / bearerTokenRaw bearer token, or null
authenticateBearerJwt(secret)Authorization Bearer JWT
authenticateCookieJwt(cookieName, jwtSecret, cookieSecret?)JWT in a (optionally signed) cookie
claim(name, default?), hasClaim(name)Claim lookup
hasRole(role), requireRole(role)requireRole throws 403 Forbidden
hasPermission(p), requirePermission(p)Same pattern for permissions
requireVerified()Throws 401 if anonymous
setVerifiedSub(sub)Trust-boundary helper after an upstream check
setAnonymous() / clear()Clear the principal
function requireAuth(req, res, next) {
    req.auth.authenticateBearerJwt("my-jwt-secret");
    next();
}

@GET("/api/admin/stats")
function adminStats(req) {
    req.auth.requireRole("admin");
    return {
        "users": 2
    };
}

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

Skip public paths with use(middleware, { "except": [...] }). Exact paths and trailing * prefixes are supported. Ingress aliases X-Malda-Auth-Verified / X-Malda-Auth-Sub (and legacy X-Auth-* / X-Verified-Sub) can pre-mark a subject; application middleware should still call authenticate* so roles and claims are present.

29.6.3 req.session

Available after enableSession(secret, options?). Same surface as HttpServer:

MemberNotes
idSession id
get(key) / set(key, value) / delete(key) / clear()Server-side bag
flash(key, value), getFlash(key), getFlashes()One-request notices (typical after redirect)

29.7 Response Handling

29.7.1 Return shapes

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

29.7.2 Response helper API

MemberNotes
status(code)Set status; chainable
json / text / html / redirect / send / sendBase64 / fragmentSend and close immediately
header(name, value), setContentType(type)Headers before send
cookie(name, value, options?), clearCookie(name, options?)Defaults: HttpOnly=true, Secure=true, SameSite=Lax, Path=/. Options: domain, maxAge, sameSite, httpOnly, secure, path
flush()Send whatever is already committed (including status-only)
statusCode, contentType / type, headers, bodyReadable snapshot
committed / sent / isCommitted, flushed / isFlushedSend state

res.send(value) UTF-8-encodes a string body. Use res.sendBase64(b64) for binary payloads (PDF, DOCX, and similar): it decodes the base64 string and writes those bytes. Invalid base64 throws.

After a send helper, the handler may keep running (background work). Headers and cookies set after send throw response already sent. res.fragment(targetId, html) is the early-send form of componentFragment(targetId, html): same X-Malda-Fragment headers, but the client receives the fragment before the handler returns.

29.8 Middleware

Middleware is a shared function that runs before the route handler — see 29.1.1 if the term is new. Use server.use(...) for global middleware. The contract is (req, res, next). Call next() to continue; omitting it short-circuits. Route-level @Use("name") runs after global middleware and before validation + the handler. Transpiled apps should pass the function name as a string: server.use("requireAuth").

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

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

29.9 CSRF, Rate Limiting, and Trusted Proxy

What these defenses are, in plain language: CSRF, rate limiting and trusted proxies.

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();

CSRF runs before middleware. Safe methods (GET/HEAD/OPTIONS) issue or refresh the CSRF cookie when missing or invalid. State-changing methods (POST/PUT/PATCH/DELETE) require a matching cookie plus request token from X-CSRF-Token, body.csrfToken, or body._csrf. Pair this with generateCsrfToken / csrfField from chapter 13 for browser clients.

Rate limiting runs after global and route middleware. verifiedSub* strategies key on the subject set by authenticateBearerJwt / authenticateCookieJwt; otherwise they fall back to IP/token. user keys on the X-User-Id header. Proxy headers are ignored until configureTrustedProxy(...) is enabled.

CSRF, auth, and rate-limit failures use the standard error envelope and include the correlation ID.

29.10 Validation

@Validate(...) checks path, query, and body before the handler. Invalid requests return HTTP 400 with field-level details and do not run handler logic. Schema values may be a JSON string or an object. Rules are a pipe DSL (int|required|min=1) or an object {type, required, min, max, minLength, maxLength, pattern}.

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

For checks that depend on loaded data, call req.requireValid(schema) or req.validate(schema) inside the handler. Validation details items are {location, field, message}. When Swagger is enabled, @Validate metadata is reflected in OpenAPI parameter and request-body schemas.

29.11 Error Handling

Framework failures (404, CSRF, auth, rate limit, @Validate / req.requireValid, uncaught handler errors) use one JSON contract:

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

A route miss is {"status": 404, "error": "NotFound", ...}. Uncaught throw of a plain object is not a custom HTTP status: it becomes 500 InternalServerError. To send a structured client error, return the standard envelope or use res.status(code).json(...):

@GET("/api/users/{id}")
function getUserById(id, res) {
    if (!userExists(id)) {
        return {
            "status": 404,
            "error": "NotFound",
            "message": "User not found"
        };
    }
    return res.json({
        "id": id
    });
}

req.auth.requireRole, requirePermission, requireVerified, and req.requireValid already throw that envelope.

29.12 CORS and Swagger/OpenAPI

CORS is the browser's cross-origin gate; host-side RestClient ignores it. Primer: 29.1.1 CORS.

var server = new RestServer(8080);
server.enableCORS(true);
server.setCORSOrigin("*");
server.setCORSMethods(["GET", "POST", "OPTIONS"]);
server.setCORSHeaders(["Content-Type", "Authorization"]);
server.enableSwagger(true);
server.start();

// OpenAPI JSON: /swagger.json, /openapi.json, /swagger/openapi.json

With Swagger enabled, GET those three paths (same document). Validation constraints from @Validate appear in the generated schemas.

29.13 Mounting on HttpServer

For a single listener that serves API routes first, then pages/components/static files, construct a deferred RestServer and mount it. Do not give the API its own port, and do not start() it as a standalone listener before mount.

var rest = new RestServer();
var web = new HttpServer(8080);

function requireAuth(req, res, next) {
    req.auth.authenticateBearerJwt(jwtSecret);
    next();
}

@GET("/api/health")
function health() {
    return {
        "status": "ok"
    };
}

web.mount(rest);
web.enableSession(sessionSecret);
rest.enableCsrf(csrfSecret);
rest.setRateLimit(200, 60, "verifiedSubOrIp");
rest.use(requireAuth, {
    "except": ["/api/health"]
});
web.start();

29.14 Interpret vs Transpile

29.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);
}

Richer starters in the repo:

29.16 Password, JWT, and cookie helpers

These helpers implement the tokens and hashes that this chapter and 27. HttpServer wire into HTTP. Primers: password hashing, JWT, CSRF. Handler surface: 29.6.2 req.auth. Name index: 13.25.

var passwordHash = hashPassword("my-password");              // PBKDF2-SHA256 hash
var isValid = verifyPassword("my-password", passwordHash);   // true/false

var claims = {"sub": "user-123", "role": "admin"};
var token = createJwt(claims, "my-jwt-secret", 3600);        // expires in 3600s
var token2 = createJwt(claims, "my-jwt-secret", {
  "expiresInSeconds": 3600,
  "issuer": "malda-core",
  "audience": "malda-apps",
  "notBeforeSeconds": 0
});
var verifiedClaims = verifyJwt(token2, "my-jwt-secret", {
  "issuer": "malda-core",
  "audience": "malda-apps"
});
print(verifiedClaims.sub);

var csrfToken = generateCsrfToken("csrf-secret", 3600);
var csrfOk = verifyCsrfToken(csrfToken, "csrf-secret");      // true/false

var setCookieHeader = createSecureCookie("session", "user-123", "cookie-secret");
// send with res.header("Set-Cookie", setCookieHeader) or use res.cookie(...)
var cookieValue = split(split(setCookieHeader, ";")[0], "=")[1];
var session = readSecureCookie(urlDecode(cookieValue), "cookie-secret");
Security function notes:

See Also