userland.run70Try the demo
Example · json-api

A Node.js HTTP server + dashboard, previewed in the page

node · host JS engine

A plain http.createServer() app with two routes: /api/stats returns JSON (Node version, engine, request counts) and / serves a dashboard that polls it once a second and charts the real requests per second.

Nothing leaves the tab. The server listens inside NanoVM; the SDK's ServeBridge and service worker route the preview iframe's requests to it, so the dashboard's fetch() calls are answered by the same Node process.

Run it

dev server

Start the server and open the dashboard

The preview below is served by this Node program, running in your tab.

javascript
// A Node.js HTTP server — no dependencies — exposing a JSON API plus a
// dashboard that polls it. Runs inside NanoVM (on the host-engine Node tier or
// the RISC-V VM); surfaced to the preview iframe through the service-worker bridge.
const http = require("http");

const start = Date.now();
let requests = 0;
// Requests per second over the last 16 seconds (real counts, drawn as the chart).
const perSecond = new Map();
function series() {
  const now = Math.floor(Date.now() / 1000);
  return Array.from({ length: 16 }, (_, i) => perSecond.get(now - 15 + i) || 0);
}

function stats() {
  return {
    runtime: "Node.js " + process.version,
    engine: process.arch === "riscv64" ? "RISC-V VM (riscv64 Node ELF)" : "host JS engine",
    platform: process.platform + " " + process.arch,
    uptimeMs: Date.now() - start,
    requests: requests,
    series: series(),
  };
}

const PAGE = [
  "<!doctype html><html><head><meta charset='utf-8'>",
  "<meta name='viewport' content='width=device-width, initial-scale=1'>",
  "<title>nano - json-api</title><style>",
  "*{box-sizing:border-box}body{margin:0;font-family:ui-monospace,monospace;background:#0e0e10;color:#cfcfda}",
  ".wrap{max-width:620px;margin:0 auto;padding:28px 20px}",
  "h1{color:#5fd6d1;font-size:18px;margin:0 0 4px;letter-spacing:.02em}",
  ".sub{color:#8c8c97;font-size:12px;margin-bottom:18px}",
  ".grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:16px}",
  ".card{background:#16161c;border:1px solid #2a2a31;border-radius:10px;padding:12px 14px}",
  ".k{color:#8c8c97;font-size:10px;text-transform:uppercase;letter-spacing:.12em}",
  ".v{color:#e8e6f0;font-size:15px;margin-top:4px}",
  ".v.hi{color:#a984f5}",
  "canvas{width:100%;height:120px;background:#16161c;border:1px solid #2a2a31;border-radius:10px}",
  ".tag{display:inline-block;margin-top:14px;font-size:11px;color:#e6b45a;border:1px solid #4a3b1e;border-radius:999px;padding:3px 10px}",
  "</style></head><body><div class='wrap'>",
  "<h1>JSON API + dashboard</h1>",
  "<div class='sub'>Served by a Node.js http server running inside NanoVM, polled live over fetch().</div>",
  "<div class='grid' id='cards'></div>",
  "<canvas id='chart' width='580' height='120'></canvas>",
  "<div class='tag'>node http server &middot; no backend</div>",
  "</div><script>",
  "function card(k,v,hi){return \"<div class='card'><div class='k'>\"+k+\"</div><div class='v\"+(hi?' hi':'')+\"'>\"+v+\"</div></div>\"}",
  "function draw(s){var c=document.getElementById('chart'),x=c.getContext('2d'),w=c.width,h=c.height;x.clearRect(0,0,w,h);",
  "var max=Math.max.apply(null,s.concat([1])),bw=w/s.length;x.fillStyle='#a984f5';",
  "for(var i=0;i<s.length;i++){var bh=(s[i]/max)*(h-16);x.globalAlpha=.55+.45*(i/s.length);x.fillRect(i*bw+3,h-bh,bw-6,bh)}x.globalAlpha=1}",
  "async function tick(){try{var d=await (await fetch('api/stats')).json();",
  "document.getElementById('cards').innerHTML=card('runtime',d.runtime,1)+card('engine',d.engine)+card('platform',d.platform)+card('requests served',d.requests,1);",
  "draw(d.series)}catch(e){}}",
  "setInterval(tick,1000);tick();",
  "</script></body></html>",
].join("");

const server = http.createServer(function (req, res) {
  requests++;
  const sec = Math.floor(Date.now() / 1000);
  perSecond.set(sec, (perSecond.get(sec) || 0) + 1);
  perSecond.delete(sec - 60);
  if (req.url.indexOf("/api/stats") !== -1) {
    res.writeHead(200, { "Content-Type": "application/json" });
    res.end(JSON.stringify(stats()));
    return;
  }
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
  res.end(PAGE);
});

server.listen(3000, function () {
  console.log("api listening on 3000");
});
loading…
Expected output
JSON API + dashboard

How it runs

  1. server.js is written to the VFS and started with nano.node() on the host JS engine; it listens on port 3000 in the kernel's port table.
  2. ServeBridge.register() connects the service worker at /nano-sw.js to the server; bridge.previewUrl(3000) gives the iframe URL.
  3. The server runs until you press Stop.

Source

From demos/json-api/.

server.js
javascript
// A Node.js HTTP server — no dependencies — exposing a JSON API plus a
// dashboard that polls it. Runs inside NanoVM (on the host-engine Node tier or
// the RISC-V VM); surfaced to the preview iframe through the service-worker bridge.
const http = require("http");

const start = Date.now();
let requests = 0;
// Requests per second over the last 16 seconds (real counts, drawn as the chart).
const perSecond = new Map();
function series() {
  const now = Math.floor(Date.now() / 1000);
  return Array.from({ length: 16 }, (_, i) => perSecond.get(now - 15 + i) || 0);
}

function stats() {
  return {
    runtime: "Node.js " + process.version,
    engine: process.arch === "riscv64" ? "RISC-V VM (riscv64 Node ELF)" : "host JS engine",
    platform: process.platform + " " + process.arch,
    uptimeMs: Date.now() - start,
    requests: requests,
    series: series(),
  };
}

const PAGE = [
  "<!doctype html><html><head><meta charset='utf-8'>",
  "<meta name='viewport' content='width=device-width, initial-scale=1'>",
  "<title>nano - json-api</title><style>",
  "*{box-sizing:border-box}body{margin:0;font-family:ui-monospace,monospace;background:#0e0e10;color:#cfcfda}",
  ".wrap{max-width:620px;margin:0 auto;padding:28px 20px}",
  "h1{color:#5fd6d1;font-size:18px;margin:0 0 4px;letter-spacing:.02em}",
  ".sub{color:#8c8c97;font-size:12px;margin-bottom:18px}",
  ".grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:16px}",
  ".card{background:#16161c;border:1px solid #2a2a31;border-radius:10px;padding:12px 14px}",
  ".k{color:#8c8c97;font-size:10px;text-transform:uppercase;letter-spacing:.12em}",
  ".v{color:#e8e6f0;font-size:15px;margin-top:4px}",
  ".v.hi{color:#a984f5}",
  "canvas{width:100%;height:120px;background:#16161c;border:1px solid #2a2a31;border-radius:10px}",
  ".tag{display:inline-block;margin-top:14px;font-size:11px;color:#e6b45a;border:1px solid #4a3b1e;border-radius:999px;padding:3px 10px}",
  "</style></head><body><div class='wrap'>",
  "<h1>JSON API + dashboard</h1>",
  "<div class='sub'>Served by a Node.js http server running inside NanoVM, polled live over fetch().</div>",
  "<div class='grid' id='cards'></div>",
  "<canvas id='chart' width='580' height='120'></canvas>",
  "<div class='tag'>node http server &middot; no backend</div>",
  "</div><script>",
  "function card(k,v,hi){return \"<div class='card'><div class='k'>\"+k+\"</div><div class='v\"+(hi?' hi':'')+\"'>\"+v+\"</div></div>\"}",
  "function draw(s){var c=document.getElementById('chart'),x=c.getContext('2d'),w=c.width,h=c.height;x.clearRect(0,0,w,h);",
  "var max=Math.max.apply(null,s.concat([1])),bw=w/s.length;x.fillStyle='#a984f5';",
  "for(var i=0;i<s.length;i++){var bh=(s[i]/max)*(h-16);x.globalAlpha=.55+.45*(i/s.length);x.fillRect(i*bw+3,h-bh,bw-6,bh)}x.globalAlpha=1}",
  "async function tick(){try{var d=await (await fetch('api/stats')).json();",
  "document.getElementById('cards').innerHTML=card('runtime',d.runtime,1)+card('engine',d.engine)+card('platform',d.platform)+card('requests served',d.requests,1);",
  "draw(d.series)}catch(e){}}",
  "setInterval(tick,1000);tick();",
  "</script></body></html>",
].join("");

const server = http.createServer(function (req, res) {
  requests++;
  const sec = Math.floor(Date.now() / 1000);
  perSecond.set(sec, (perSecond.get(sec) || 0) + 1);
  perSecond.delete(sec - 60);
  if (req.url.indexOf("/api/stats") !== -1) {
    res.writeHead(200, { "Content-Type": "application/json" });
    res.end(JSON.stringify(stats()));
    return;
  }
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
  res.end(PAGE);
});

server.listen(3000, function () {
  console.log("api listening on 3000");
});
package.json
json
{
  "name": "nano-json-api",
  "version": "1.0.0",
  "private": true,
  "description": "A dependency-free Node.js HTTP server: a JSON API plus a dashboard that polls it."
}

Further reading

Other examples