Quick Start

Get up and running with NANO in under 5 minutes

1

Simple JavaScript App (5 min)

Create app.js

app.js
export default {
  async fetch(request) {
    const url = new URL(request.url);
    
    if (url.pathname === '/') {
      return new Response('Hello from NANO!', {
        headers: { 'Content-Type': 'text/plain' }
      });
    }
    
    if (url.pathname === '/json') {
      // Return a plain object (simplest, always works)
      return {
        status: 200,
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          message: 'Hello from NANO!',
          runtime: 'nano-rs',
          time: Date.now()
        })
      };
    }
    
    return new Response('Not Found', { status: 404 });
  }
};

Create nano.json

nano.json
{
  "server": {
    "port": 8080
  },
  "apps": [
    {
      "hostname": "localhost",
      "entrypoint": "./app.js",
      "limits": {
        "workers": 2,
        "memory_mb": 64
      }
    }
  ]
}

Run

Terminal 1: Start the server
nano-rs run --config nano.json
Terminal 2: Test
# Test the root endpoint
curl http://localhost:8080/
Hello from NANO!

# Test JSON endpoint
curl http://localhost:8080/json
{"message":"Hello from NANO!","time":1234567890}
2

WebAssembly App (10 min)

Compile Rust to WASM and call it from JavaScript.

Create Rust library

wasm/Cargo.toml
[package]
name = "calc"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2"
wasm/src/lib.rs
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u64 {
    match n {
        0 => 0,
        1 => 1,
        _ => fibonacci(n - 1) + fibonacci(n - 2),
    }
}

Compile

Build WASM module
cd wasm
wasm-pack build --target web --out-dir ../pkg

Create JavaScript handler

wasm-app.js
import wasmModule from './pkg/calc.js';

let wasm;
let initPromise;

async function initWasm() {
  wasm = await wasmModule();
}

export default {
  async fetch(request) {
    // Initialize on first request
    if (!initPromise) {
      initPromise = initWasm();
    }
    await initPromise;
    
    const url = new URL(request.url);
    
    if (url.pathname === '/add') {
      const a = parseInt(url.searchParams.get('a') || '0');
      const b = parseInt(url.searchParams.get('b') || '0');
      const result = wasm.add(a, b);
      return Response.json({ a, b, result });
    }
    
    if (url.pathname === '/fib') {
      const n = parseInt(url.searchParams.get('n') || '10');
      const result = wasm.fibonacci(n);
      return Response.json({ n, result });
    }
    
    return new Response('Usage: /add?a=5&b=3 or /fib?n=20', {
      headers: { 'Content-Type': 'text/plain' }
    });
  }
};

Create config

nano-wasm.json
{
  "server": { "port": 8080 },
  "apps": [{
    "hostname": "localhost",
    "entrypoint": "./wasm-app.js",
    "limits": { "workers": 2, "memory_mb": 128 }
  }]
}

Run

Terminal 1: Start
nano-rs run --config nano-wasm.json
Terminal 2: Test WASM calls
# Add two numbers
curl "http://localhost:8080/add?a=5&b=3"
{"a":5,"b":3,"result":8}

# Calculate fibonacci
curl "http://localhost:8080/fib?n=20"
{"n":20,"result":6765}
3

Slivers: Create and Deploy (5 min)

Slivers are portable snapshots with ~267µs cold starts. Run an app, then create and deploy a sliver.

Create app

sliver-demo.js
export default {
  async fetch(request) {
    const url = new URL(request.url);
    
    if (url.pathname === '/') {
      return new Response('Hello from Sliver!', {
        headers: { 'Content-Type': 'text/plain' }
      });
    }
    
    if (url.pathname === '/version') {
      return Response.json({ 
        version: '1.0.0',
        deployed: new Date().toISOString()
      });
    }
    
    return new Response('Not Found', { status: 404 });
  }
};

Step 1: Run the app for testing

dev.json
{
  "server": { "port": 8080 },
  "apps": [{
    "hostname": "localhost",
    "entrypoint": "./sliver-demo.js",
    "limits": { "workers": 2 }
  }]
}

Terminal 1: nano-rs run --config dev.json

Step 2: Create the sliver

Terminal 2: Create sliver from running app
# Create sliver from the running app
nano-rs sliver create localhost --output my-app-v1.sliver

# Verify it was created
ls -lh my-app-v1.sliver

Step 3: Stop the dev server

# In Terminal 1, press
Ctrl+C

Step 4: Run from sliver

Terminal 1: Run directly from sliver (no config!)
nano-rs run --sliver my-app-v1.sliver --port 8080
Terminal 2: Test - identical behavior!
curl http://localhost:8080/
Hello from Sliver!

curl http://localhost:8080/version
{"version":"1.0.0","deployed":"2026-01-01T12:00:00Z"}

Quick Command Reference

Commands

nano-rs run --config nano.json - Run with config
nano-rs run --sliver app.sliver - Run from sliver
nano-rs sliver create localhost --output app.sliver - Create sliver

Testing

curl http://localhost:8080/ - Basic test
curl -H "Host: api.example.com" ... - Multi-tenant
curl http://localhost:8080/json - JSON endpoint

Next Steps

?

Understanding Workers

What is a Worker?

A worker is a dedicated OS thread that:

  • Owns one V8 isolate (JavaScript execution environment)
  • Processes requests sequentially in a loop
  • Cannot share isolates with other workers (V8 requirement)

Request Routing (Round-Robin)

NANO uses round-robin routing to distribute requests:

Request 1 → Worker 0
Request 2 → Worker 1  
Request 3 → Worker 2
Request 4 → Worker 0 (wraps around)

Key Points:

  • No shared state between workers (isolation)
  • Same hostname can hit different workers
  • Memory is per-isolate (4 workers = 4× memory)

Understanding the Logs

When you run NANO, you'll see two types of logs:

HTTP Access Logs (includes worker_id)
HTTP GET / → 200 in 5.23ms (worker: 0)
HTTP GET /json → 200 in 3.45ms (worker: 1)
HTTP GET /notfound → 404 in 1.20ms (worker: 2)
Worker Processing Logs
Worker 0 processed GET / → 200 in 5ms (isolate: worker_0_localhost)
Worker 1 processed GET /json → 200 in 3ms (isolate: worker_1_localhost)

Log Fields

HTTP Access Log

  • method - GET, POST, etc.
  • path - Request path
  • host - Virtual hostname
  • status - HTTP code (200, 404...)
  • worker_id - Which worker (0, 1, 2...)
  • duration_ms - Total time

Worker Processing Log

  • worker_id - Thread (0, 1, 2...)
  • isolate_id - V8 isolate name
  • hostname - Tenant
  • method/path - Request
  • status - Response code
  • duration_ms - JS exec time

Configuring Workers

nano.json
{
  "apps": [{
    "hostname": "localhost",
    "entrypoint": "./app.js",
    "limits": {
      "workers": 4,
      "memory_mb": 128
    }
  }]
}

Low Traffic

1-2 workers

Standard

4 workers (default)

High Traffic

8-16 workers

Memory Warning: Each worker = 1 isolate. With 4 workers and 128MB limit, that's ~512MB total memory usage.

Understanding Logs

NANO uses structured JSON logging with request tracing. Each request generates multiple log entries showing the complete lifecycle.

Log Format

{
  "ts": "2026-05-03T12:34:56.789Z",
  "level": "INFO",
  "message": "HTTP GET / - 200 in 5.23ms (worker: 0, isolate: iso_a3f7b2d8_00000001)",
  "request_id": "req_a3f7b2d8",
  "worker_id": 0,
  "isolate_id": "iso_a3f7b2d8_00000001",
  "hostname": "localhost",
  "fields": {
    "method": "GET",
    "path": "/",
    "status": 200,
    "duration_ms": "5.23",
    "worker_id": 0,
    "isolate_id": "iso_a3f7b2d8_00000001"
  }
}

Key Fields

request_id

Unique hash: req_{uuid_first_8}

worker_id

Worker thread number (0, 1, 2...)

isolate_id

Unique V8 isolate hash: iso_{uuid}_{counter}

hostname

Virtual host the request was routed to

Log Types

HTTP Access log showing method, path, status, duration with worker_id/isolate_id
Worker Worker thread log showing request processing details
V8 JavaScript execution log with memory usage and errors

Debugging Isolates

Use the three-part combo for full request tracing:

request_id - Tracks single HTTP request
worker_id - OS thread (never changes)
isolate_id - V8 isolate (changes on OOM)

See Debugging and Profiling NANO Isolates for complete guide.

Troubleshooting

"unknown field `port`"

Put port inside server: {"server": {"port": 8080}}

Port already in use

lsof -ti:8080 | xargs kill -9

"Cannot find module"

Use ./ prefix: "entrypoint": "./app.js"