skills/farming-labs/fm-skills/universal-javascript-runtimes

universal-javascript-runtimes

SKILL.md

Universal JavaScript Runtimes

Overview

Modern JavaScript can run in many environments: Node.js, Deno, Bun, Cloudflare Workers, browsers, and more. Universal JavaScript means writing code once that runs everywhere.

The Runtime Fragmentation Problem

JAVASCRIPT RUNTIMES (2024):

┌─────────────────────────────────────────────────────────────────┐
│                    Traditional Servers                           │
├─────────────────────────────────────────────────────────────────┤
│  Node.js         │ The original server runtime (2009)           │
│  Deno            │ Secure runtime by Node creator (2020)        │
│  Bun             │ Fast all-in-one runtime (2022)               │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                    Edge/Serverless                               │
├─────────────────────────────────────────────────────────────────┤
│  Cloudflare Workers  │ V8 isolates at the edge                  │
│  Vercel Edge         │ V8 isolates, Next.js optimized           │
│  Deno Deploy         │ Deno at the edge                         │
│  Netlify Edge        │ Deno-based edge functions                │
│  AWS Lambda          │ Node.js/custom runtimes                  │
│  Fastly Compute      │ WebAssembly-based                        │
└─────────────────────────────────────────────────────────────────┘

PROBLEM: Each has different APIs!

// Node.js
const fs = require('fs');
const http = require('http');

// Deno
await Deno.readFile('file.txt');
Deno.serve(handler);

// Cloudflare Workers
export default { fetch(request, env, ctx) {} }

// Bun
Bun.serve({ fetch(req) {} });

Web Standards: The Universal Foundation

The solution is web standard APIs that all runtimes implement:

// WEB STANDARD APIs (work everywhere):

// Fetch API
const response = await fetch('https://api.example.com/data');
const data = await response.json();

// Request/Response
const request = new Request('https://example.com', {
  method: 'POST',
  body: JSON.stringify({ key: 'value' }),
});
const response = new Response('Hello', { status: 200 });

// URL
const url = new URL('https://example.com/path?query=value');
console.log(url.pathname);  // '/path'

// Headers
const headers = new Headers();
headers.set('Content-Type', 'application/json');

// TextEncoder/TextDecoder
const encoder = new TextEncoder();
const bytes = encoder.encode('Hello');

// Crypto
const hash = await crypto.subtle.digest('SHA-256', data);

// Streams
const stream = new ReadableStream({ /* ... */ });

// AbortController
const controller = new AbortController();
fetch(url, { signal: controller.signal });

WinterCG (Web-interoperable Runtimes Community Group)

WinterCG standardizes APIs across server runtimes:

WINTERCG MEMBERS:
- Cloudflare
- Deno
- Node.js
- Vercel
- Shopify
- Bloomberg
- ... and more

STANDARDIZED APIs:
├── fetch(), Request, Response, Headers
├── URL, URLSearchParams, URLPattern
├── TextEncoder, TextDecoder
├── crypto.subtle (Web Crypto API)
├── Streams (ReadableStream, WritableStream)
├── AbortController, AbortSignal
├── setTimeout, setInterval
├── console
├── structuredClone
├── atob, btoa
└── Performance API

RESULT: Code using these APIs works on ANY compliant runtime

The UnJS Ecosystem

UnJS (Universal JavaScript) is a collection of packages for building universal JavaScript:

UNJS ECOSYSTEM:

┌─────────────────────────────────────────────────────────────────┐
│                        FRAMEWORKS                                │
├─────────────────────────────────────────────────────────────────┤
│  Nitro       │ Universal server builder (powers Nuxt)           │
│  H3          │ Minimal HTTP framework                           │
│  Nuxt        │ Full-stack Vue framework (uses Nitro)           │
│  Analog      │ Angular meta-framework (uses Nitro)              │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                      CORE UTILITIES                              │
├─────────────────────────────────────────────────────────────────┤
│  ofetch      │ Better fetch with auto-retry, interceptors       │
│  unenv       │ Runtime environment polyfills                    │
│  unbuild     │ Unified build system for libraries               │
│  unimport    │ Auto-import utilities                            │
│  unstorage   │ Universal storage layer                          │
│  uncrypto    │ Universal crypto utilities                       │
│  unhead      │ Universal document head manager                  │
│  unctx       │ Composable async context                         │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                     INFRASTRUCTURE                               │
├─────────────────────────────────────────────────────────────────┤
│  listhen     │ Universal HTTP server listener                   │
│  ufo         │ URL utilities                                    │
│  pathe       │ Cross-platform path utilities                    │
│  consola     │ Universal logger                                 │
│  defu        │ Deep defaults merging                            │
│  hookable    │ Async hooks system                               │
│  c12         │ Config loading utility                           │
└─────────────────────────────────────────────────────────────────┘

H3: The Universal HTTP Framework

H3 is a minimal, high-performance HTTP framework that runs everywhere:

// H3 BASICS:

import { createApp, createRouter, defineEventHandler } from 'h3';

const app = createApp();
const router = createRouter();

// Define routes
router.get('/hello', defineEventHandler((event) => {
  return { message: 'Hello World' };
}));

router.post('/users', defineEventHandler(async (event) => {
  const body = await readBody(event);
  return { created: body };
}));

app.use(router);

// H3 is runtime-agnostic - it works with:
// - Node.js http server
// - Bun.serve
// - Deno.serve  
// - Cloudflare Workers
// - Any web standard runtime

H3 Event Handler Model

// H3 uses an "event" abstraction over Request/Response:

import {
  defineEventHandler,
  getQuery,
  getRouterParams,
  readBody,
  setCookie,
  setHeader,
  createError,
  sendRedirect,
} from 'h3';

// Request utilities
const handler = defineEventHandler(async (event) => {
  // URL query parameters
  const query = getQuery(event);  // ?page=1 → { page: '1' }
  
  // Route parameters
  const params = getRouterParams(event);  // /users/:id → { id: '123' }
  
  // Request body (auto-parsed)
  const body = await readBody(event);  // JSON, FormData, etc.
  
  // Request headers
  const auth = getHeader(event, 'authorization');
  
  // Response utilities
  setHeader(event, 'X-Custom', 'value');
  setCookie(event, 'session', 'abc123');
  
  // Return response (auto-serialized)
  return { success: true };  // Becomes JSON response
});

// Error handling
const protectedHandler = defineEventHandler((event) => {
  const user = getUser(event);
  if (!user) {
    throw createError({
      statusCode: 401,
      message: 'Unauthorized',
    });
  }
  return { user };
});

// Redirects
const redirectHandler = defineEventHandler((event) => {
  return sendRedirect(event, '/new-location', 302);
});

Why H3 Over Express/Fastify?

EXPRESS (Traditional Node.js):
┌─────────────────────────────────────────────────────────────────┐
│  - Built on Node.js http module                                 │
│  - req/res are Node-specific objects                           │
│  - Middleware modifies req/res                                  │
│  - Large ecosystem but Node-only                                │
│  - Callback-based (older pattern)                               │
│                                                                  │
│  LIMITATION: Only works on Node.js                              │
└─────────────────────────────────────────────────────────────────┘

H3 (Universal):
┌─────────────────────────────────────────────────────────────────┐
│  - Built on web standards (Request/Response)                   │
│  - Event abstraction over different runtimes                   │
│  - Composable with defineEventHandler                          │
│  - Tree-shakeable (small bundle)                               │
│  - TypeScript-first                                             │
│                                                                  │
│  ADVANTAGE: Works everywhere via adapters                       │
└─────────────────────────────────────────────────────────────────┘

// Express (Node.js only):
app.get('/api', (req, res) => {
  res.json({ message: 'Hello' });
});

// H3 (Universal):
router.get('/api', defineEventHandler(() => {
  return { message: 'Hello' };  // Works on Node, Deno, Workers, etc.
}));

Nitro: Universal Server Builder

Nitro is the server framework that powers Nuxt. It builds universal server applications:

NITRO OVERVIEW:

┌─────────────────────────────────────────────────────────────────┐
│                      YOUR CODE                                   │
│                                                                  │
│   server/                                                        │
│   ├── api/           API routes (auto-registered)               │
│   │   ├── hello.ts   → /api/hello                               │
│   │   └── users/                                                │
│   │       └── [id].ts → /api/users/:id                          │
│   ├── routes/        Custom routes                              │
│   ├── middleware/    Server middleware                          │
│   ├── plugins/       Server plugins                             │
│   └── utils/         Server utilities                           │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│                     NITRO BUILD                                  │
│                                                                  │
│   1. Scan routes and handlers                                   │
│   2. Bundle server code                                         │
│   3. Apply runtime-specific transforms                          │
│   4. Generate optimized output                                  │
└─────────────────────────────────────────────────────────────────┘
              ┌───────────────┴───────────────┐
              │         PRESETS               │
              ├───────────────────────────────┤
              │  node-server    │ Node.js     │
              │  cloudflare     │ Workers     │
              │  vercel         │ Vercel      │
              │  netlify        │ Netlify     │
              │  deno           │ Deno Deploy │
              │  bun            │ Bun         │
              │  aws-lambda     │ Lambda      │
              │  ... 20+ more   │             │
              └───────────────────────────────┘

Nitro File-Based Routing

server/
├── api/
│   ├── hello.ts              → GET  /api/hello
│   ├── hello.post.ts         → POST /api/hello
│   ├── users/
│   │   ├── index.ts          → GET  /api/users
│   │   ├── index.post.ts     → POST /api/users
│   │   └── [id].ts           → GET  /api/users/:id
│   └── [...slug].ts          → /api/* (catch-all)
├── routes/
│   └── feed.xml.ts           → GET  /feed.xml
└── middleware/
    └── auth.ts               → Runs on all requests
// server/api/users/[id].ts
export default defineEventHandler(async (event) => {
  const id = getRouterParam(event, 'id');
  const user = await db.users.findById(id);
  
  if (!user) {
    throw createError({
      statusCode: 404,
      message: 'User not found',
    });
  }
  
  return user;
});

// server/middleware/auth.ts
export default defineEventHandler((event) => {
  const token = getHeader(event, 'authorization');
  if (token) {
    event.context.user = verifyToken(token);
  }
});

Nitro Deployment Presets

// nitro.config.ts
export default defineNitroConfig({
  // Target platform
  preset: 'cloudflare-pages',  // or 'vercel', 'netlify', 'node', etc.
  
  // Preset-specific options
  cloudflare: {
    pages: {
      routes: {
        exclude: ['/static/*'],
      },
    },
  },
});

// BUILD COMMAND:
// npx nitro build

// OUTPUT varies by preset:

// preset: 'node-server'
// → .output/
//   ├── server/
//   │   └── index.mjs     (Node.js server)
//   └── public/           (Static files)

// preset: 'cloudflare-pages'  
// → .output/
//   ├── _worker.js        (Workers script)
//   └── public/           (Static files)

// preset: 'vercel'
// → .vercel/
//   └── output/
//       ├── functions/    (Serverless functions)
//       └── static/       (Static files)

How Nitro Achieves Universality

NITRO'S UNIVERSAL ARCHITECTURE:

┌─────────────────────────────────────────────────────────────────┐
│                     YOUR H3 HANDLERS                             │
│                                                                  │
│   // Same code for all platforms                                │
│   defineEventHandler((event) => {                               │
│     return { message: 'Hello' };                                │
│   });                                                           │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│                        H3 LAYER                                  │
│                                                                  │
│   Abstracts Request/Response handling                           │
│   Provides consistent event interface                           │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│                       UNENV LAYER                                │
│                                                                  │
│   Polyfills Node.js APIs for non-Node runtimes                 │
│   - process.env → runtime env vars                              │
│   - Buffer → Uint8Array                                         │
│   - fs → unstorage                                              │
│   - crypto → Web Crypto                                         │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│                    RUNTIME ADAPTER                               │
│                                                                  │
│   Node.js:     http.createServer(toNodeHandler(app))            │
│   Deno:        Deno.serve(toWebHandler(app))                    │
│   Workers:     export default { fetch: toWebHandler(app) }      │
│   Bun:         Bun.serve({ fetch: toWebHandler(app) })          │
└─────────────────────────────────────────────────────────────────┘

unenv: The Environment Polyfill Layer

unenv provides Node.js API compatibility for non-Node runtimes:

// THE PROBLEM:

// Your code uses Node.js APIs:
import { readFileSync } from 'fs';
import { createHash } from 'crypto';

// But Cloudflare Workers doesn't have fs or crypto modules!
// ❌ Error: Cannot find module 'fs'


// UNENV SOLUTION:

// unenv provides mock/polyfill implementations:

// fs → Mock (errors or uses storage abstraction)
// crypto → Web Crypto API wrapper
// process → Minimal process-like object
// Buffer → Uint8Array wrapper

// In nitro.config.ts:
export default defineNitroConfig({
  preset: 'cloudflare',
  // unenv automatically applies polyfills
});

// Now your Node.js code "just works" on Workers
// (with some limitations for truly Node-specific APIs)

What unenv Polyfills

NODE.JS MODULE        → UNENV REPLACEMENT
──────────────────────────────────────────────────────────────────
process              → Minimal process object with env
Buffer               → Uint8Array-based implementation
stream               → Web Streams API
crypto               → Web Crypto API
path                 → Pure JS implementation
url                  → Native URL class
util                 → Pure JS utilities
events               → EventEmitter implementation
assert               → Pure JS assert

NODE.JS MODULE        → UNENV MOCK (no-op or error)
──────────────────────────────────────────────────────────────────
fs                   → Mock (use unstorage instead)
child_process        → Mock (not available in edge)
net                  → Mock (not available in edge)
http                 → Use fetch instead

unstorage: Universal Storage

unstorage provides a unified storage API across different backends:

// UNSTORAGE BASICS:

import { createStorage } from 'unstorage';
import fsDriver from 'unstorage/drivers/fs';
import redisDriver from 'unstorage/drivers/redis';
import cloudflareKVDriver from 'unstorage/drivers/cloudflare-kv-binding';

// Development: File system
const devStorage = createStorage({
  driver: fsDriver({ base: './data' }),
});

// Production (Redis):
const prodStorage = createStorage({
  driver: redisDriver({ url: 'redis://localhost:6379' }),
});

// Cloudflare Workers:
const edgeStorage = createStorage({
  driver: cloudflareKVDriver({ binding: 'MY_KV' }),
});

// SAME API everywhere:
await storage.setItem('user:123', { name: 'John' });
const user = await storage.getItem('user:123');
await storage.removeItem('user:123');
const keys = await storage.getKeys('user:');

Storage Drivers

DRIVER                 USE CASE
──────────────────────────────────────────────────────────────────
memory                Local development, testing
fs                    Node.js file system
redis                 Production caching
cloudflare-kv-binding Cloudflare Workers KV
vercel-kv             Vercel KV storage
netlify-blobs         Netlify Blob storage
planetscale           PlanetScale database
mongodb               MongoDB collections
s3                    AWS S3 buckets
github                GitHub repository files
http                  Remote HTTP storage
lru-cache             In-memory LRU cache

ofetch: Universal Fetch

ofetch is an enhanced fetch that works everywhere:

import { ofetch } from 'ofetch';

// BASIC USAGE (same as fetch):
const data = await ofetch('/api/users');

// AUTO-PARSING:
// JSON responses automatically parsed
const users = await ofetch('/api/users');  // Returns parsed JSON

// BASE URL:
const api = ofetch.create({ baseURL: 'https://api.example.com' });
const user = await api('/users/123');

// RETRY:
const data = await ofetch('/api/data', {
  retry: 3,
  retryDelay: 1000,
});

// INTERCEPTORS:
const api = ofetch.create({
  onRequest({ options }) {
    options.headers.set('Authorization', `Bearer ${token}`);
  },
  onResponse({ response }) {
    console.log('Response:', response.status);
  },
  onResponseError({ response }) {
    if (response.status === 401) {
      logout();
    }
  },
});

// TYPE-SAFE:
interface User {
  id: number;
  name: string;
}
const user = await ofetch<User>('/api/users/123');
// user is typed as User

Comparing Server Approaches

┌────────────┬──────────────┬────────────────┬─────────────────┐
│            │   Express    │    Fastify     │       H3        │
├────────────┼──────────────┼────────────────┼─────────────────┤
│ Runtime    │ Node.js only │ Node.js only   │ Universal       │
├────────────┼──────────────┼────────────────┼─────────────────┤
│ Standards  │ Node http    │ Node http      │ Web standards   │
├────────────┼──────────────┼────────────────┼─────────────────┤
│ Bundle     │ ~200KB       │ ~100KB         │ ~20KB           │
├────────────┼──────────────┼────────────────┼─────────────────┤
│ Edge ready │ ❌           │ ❌             │ ✅              │
├────────────┼──────────────┼────────────────┼─────────────────┤
│ TypeScript │ Manual types │ Built-in       │ Built-in        │
├────────────┼──────────────┼────────────────┼─────────────────┤
│ Ecosystem  │ Huge         │ Growing        │ UnJS + adapters │
└────────────┴──────────────┴────────────────┴─────────────────┘

Deep Dive: Understanding Universal JavaScript

Why Runtime Diversity Exists

THE JAVASCRIPT RUNTIME HISTORY:

2009: Node.js
      └─► JavaScript on servers (V8 engine)
      └─► npm ecosystem explodes
      └─► Everyone uses Node-specific APIs (fs, http, etc.)

2018: Cloudflare Workers
      └─► JavaScript at the edge (V8 isolates)
      └─► No file system, limited APIs
      └─► Web standard APIs only
      └─► Startup in milliseconds (vs seconds for Node)

2020: Deno
      └─► "Fixed" Node.js design mistakes
      └─► Web standards first
      └─► Built-in TypeScript
      └─► Secure by default (permissions)

2022: Bun
      └─► Speed-focused runtime (JavaScriptCore engine)
      └─► Node.js compatible
      └─► Built-in bundler, test runner
      └─► Faster npm, native APIs

2023+: More edge runtimes
      └─► Vercel Edge Runtime
      └─► Netlify Edge Functions
      └─► AWS Lambda@Edge
      └─► Fastly Compute


THE CHALLENGE:
- Code written for Node.js doesn't work on Workers
- Code written for Workers might not use Node.js ecosystem
- Each runtime has unique features/limitations
- Deployment target affects how you write code

THE SOLUTION: Write to web standards + abstract the differences

How V8 Isolates Enable Edge Computing

TRADITIONAL NODE.JS DEPLOYMENT:

┌─────────────────────────────────────────────────────────────────┐
│                      EC2 Instance                                │
│                                                                  │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                   Node.js Process                        │    │
│  │                                                          │    │
│  │  - Full V8 engine                                       │    │
│  │  - Complete Node.js runtime                             │    │
│  │  - File system access                                   │    │
│  │  - Network access                                       │    │
│  │  - All npm packages available                          │    │
│  │                                                          │    │
│  │  Startup: 500ms - 2s (cold start)                       │    │
│  │  Memory: 128MB - 1GB                                    │    │
│  └─────────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────┘


CLOUDFLARE WORKERS (V8 Isolates):

┌─────────────────────────────────────────────────────────────────┐
│                   Cloudflare Edge Server                         │
│                                                                  │
│  ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐       │
│  │ Isolate 1 │ │ Isolate 2 │ │ Isolate 3 │ │ Isolate N │       │
│  │  (App A)  │ │  (App B)  │ │  (App A)  │ │  (App X)  │       │
│  │           │ │           │ │           │ │           │       │
│  │  ~128KB   │ │  ~256KB   │ │  ~128KB   │ │  ~512KB   │       │
│  │  5ms cold │ │  5ms cold │ │  0ms warm │ │  5ms cold │       │
│  └───────────┘ └───────────┘ └───────────┘ └───────────┘       │
│                                                                  │
│  Single V8 engine, many isolated contexts                       │
│  No file system (security)                                      │
│  Limited memory per isolate                                     │
│  Millisecond cold starts                                        │
└─────────────────────────────────────────────────────────────────┘


WHY ISOLATES ARE FAST:
1. V8 engine already warm (shared)
2. No process startup overhead
3. Minimal memory per request
4. Snapshot-based initialization
5. Thousands of isolates per machine

The Web Standard Request/Response Model

// THE WEB STANDARD MODEL:

// Everything is Request → Response

// INPUT: Request object
const request = new Request('https://example.com/api/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token',
  },
  body: JSON.stringify({ name: 'John' }),
});

// Properties available:
request.method       // 'POST'
request.url          // 'https://example.com/api/users'
request.headers      // Headers object
request.body         // ReadableStream
await request.json() // Parse body as JSON
await request.text() // Parse body as text

// OUTPUT: Response object
const response = new Response(
  JSON.stringify({ id: 1, name: 'John' }),
  {
    status: 201,
    headers: {
      'Content-Type': 'application/json',
    },
  }
);

// A UNIVERSAL HANDLER:
async function handler(request: Request): Promise<Response> {
  const url = new URL(request.url);
  
  if (url.pathname === '/api/hello') {
    return new Response('Hello World');
  }
  
  return new Response('Not Found', { status: 404 });
}

// This handler works on:
// - Cloudflare Workers: export default { fetch: handler }
// - Deno: Deno.serve(handler)
// - Bun: Bun.serve({ fetch: handler })
// - Node.js (with adapter): http.createServer(toNodeHandler(handler))

How H3 Abstracts Runtime Differences

// H3's event abstraction layer:

// H3 Event wraps different runtime request types:
interface H3Event {
  node?: {              // Node.js
    req: IncomingMessage;
    res: ServerResponse;
  };
  web?: {               // Web standard
    request: Request;
    url: URL;
  };
  context: Record<string, any>;  // Request context
}

// H3 utilities work regardless of underlying runtime:

export function getHeader(event: H3Event, name: string): string | undefined {
  // Node.js path:
  if (event.node) {
    return event.node.req.headers[name.toLowerCase()];
  }
  // Web standard path:
  if (event.web) {
    return event.web.request.headers.get(name);
  }
}

export function setHeader(event: H3Event, name: string, value: string): void {
  // Node.js path:
  if (event.node) {
    event.node.res.setHeader(name, value);
  }
  // Web standard path:
  if (event._responseHeaders) {
    event._responseHeaders.set(name, value);
  }
}

// YOUR CODE just uses the abstraction:
defineEventHandler((event) => {
  const auth = getHeader(event, 'authorization');
  setHeader(event, 'X-Custom', 'value');
  return { message: 'Hello' };
});
// Works on Node, Deno, Workers, Bun without changes

Nitro's Build Process Deep Dive

NITRO BUILD PIPELINE:

INPUT:
server/
├── api/
│   └── users.ts     // H3 handlers
├── routes/
│   └── index.ts
├── middleware/
│   └── auth.ts
└── plugins/
    └── database.ts

STEP 1: SCAN
─────────────────────────────────────────────────────────────────
- Find all route files
- Parse route patterns from file names
- Discover middleware and plugins
- Build route table

Route Table:
[
  { path: '/api/users', handler: './api/users.ts', method: 'get' },
  { path: '/', handler: './routes/index.ts', method: 'get' },
]


STEP 2: BUNDLE WITH ROLLUP
─────────────────────────────────────────────────────────────────
- Bundle all handlers into single file
- Tree-shake unused code
- Apply preset-specific transforms

rollup.config:
  input: virtual:nitro-entry
  output: format based on preset (esm, cjs, iife)


STEP 3: APPLY UNENV TRANSFORMS
─────────────────────────────────────────────────────────────────
- Replace Node.js imports with polyfills
- Inject runtime-specific code
- Remove unavailable APIs

Example transforms:
  import fs from 'fs'           → import fs from 'unenv/runtime/node/fs'
  import crypto from 'crypto'   → import crypto from 'unenv/runtime/node/crypto'


STEP 4: GENERATE RUNTIME ENTRY
─────────────────────────────────────────────────────────────────

// Node.js preset:
import { createServer } from 'http';
import { toNodeHandler } from 'h3';
import { app } from './app.mjs';
createServer(toNodeHandler(app)).listen(3000);

// Cloudflare Workers preset:
import { toWebHandler } from 'h3';
import { app } from './app.mjs';
export default { fetch: toWebHandler(app) };

// Deno preset:
import { toWebHandler } from 'h3';
import { app } from './app.mjs';
Deno.serve(toWebHandler(app));


STEP 5: OUTPUT
─────────────────────────────────────────────────────────────────
.output/
├── server/
│   ├── index.mjs        // Entry point
│   └── chunks/          // Code-split chunks
└── public/              // Static assets

Edge vs Serverless vs Traditional

┌─────────────────────────────────────────────────────────────────┐
│                    TRADITIONAL SERVER                            │
├─────────────────────────────────────────────────────────────────┤
│  Location:    Single region (us-east-1)                         │
│  Runtime:     Node.js process (long-running)                    │
│  Cold start:  0ms (always warm)                                 │
│  Scaling:     Manual or auto-scaling groups                     │
│  Cost:        Pay for uptime                                    │
│  APIs:        Full Node.js (fs, net, child_process)            │
│                                                                  │
│  Best for:    WebSocket, long computations, file processing    │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                      SERVERLESS                                  │
├─────────────────────────────────────────────────────────────────┤
│  Location:    Single or few regions                             │
│  Runtime:     Node.js container (ephemeral)                     │
│  Cold start:  100ms - 3s                                        │
│  Scaling:     Automatic, per-request                            │
│  Cost:        Pay per invocation                                │
│  APIs:        Full Node.js (fs, net, etc.)                     │
│                                                                  │
│  Best for:    Infrequent traffic, variable load, APIs          │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                        EDGE                                      │
├─────────────────────────────────────────────────────────────────┤
│  Location:    Global (200+ locations)                           │
│  Runtime:     V8 isolate (lightweight)                          │
│  Cold start:  < 10ms                                            │
│  Scaling:     Automatic, instant                                │
│  Cost:        Pay per request (very cheap)                      │
│  APIs:        Web standards only (limited)                      │
│                                                                  │
│  Best for:    Low latency, personalization, A/B tests, auth    │
└─────────────────────────────────────────────────────────────────┘

HYBRID APPROACH (Modern best practice):
┌─────────────────────────────────────────────────────────────────┐
│  Edge Layer:           Auth, routing, personalization           │
│       ↓                                                         │
│  Serverless Layer:     API routes, database queries             │
│       ↓                                                         │
│  Traditional Layer:    WebSocket, heavy computation             │
└─────────────────────────────────────────────────────────────────┘

Universal Storage Patterns

// THE STORAGE ABSTRACTION PATTERN:

// Different platforms have different storage:
// - Node.js: File system, Redis, PostgreSQL
// - Workers: KV, D1, R2
// - Vercel: Vercel KV, Postgres
// - AWS: S3, DynamoDB

// UNSTORAGE provides one API:

import { createStorage } from 'unstorage';

// Configuration varies by environment:
function createAppStorage() {
  if (process.env.CLOUDFLARE) {
    return createStorage({
      driver: cloudflareKVBindingDriver({ binding: 'CACHE' }),
    });
  }
  
  if (process.env.VERCEL) {
    return createStorage({
      driver: vercelKVDriver({ /* ... */ }),
    });
  }
  
  // Development / Node.js
  return createStorage({
    driver: fsDriver({ base: './.data' }),
  });
}

// YOUR CODE uses the same API everywhere:
const storage = createAppStorage();

export async function getUser(id: string) {
  // Check cache first
  const cached = await storage.getItem(`user:${id}`);
  if (cached) return cached;
  
  // Fetch from database
  const user = await db.users.findById(id);
  
  // Cache for 5 minutes
  await storage.setItem(`user:${id}`, user, { ttl: 300 });
  
  return user;
}

Practical: Building a Universal API

// A complete universal API example:

// server/api/posts/[id].ts
export default defineEventHandler(async (event) => {
  // Works on any runtime
  const id = getRouterParam(event, 'id');
  const method = event.method;
  
  // Universal storage
  const storage = useStorage('cache');
  
  if (method === 'GET') {
    // Check cache
    const cached = await storage.getItem(`post:${id}`);
    if (cached) {
      setHeader(event, 'X-Cache', 'HIT');
      return cached;
    }
    
    // Fetch from database (works on edge via fetch)
    const post = await ofetch(`https://api.example.com/posts/${id}`);
    
    // Cache for 1 hour
    await storage.setItem(`post:${id}`, post, { ttl: 3600 });
    
    return post;
  }
  
  if (method === 'PUT') {
    const body = await readBody(event);
    
    // Update via API
    const updated = await ofetch(`https://api.example.com/posts/${id}`, {
      method: 'PUT',
      body,
    });
    
    // Invalidate cache
    await storage.removeItem(`post:${id}`);
    
    return updated;
  }
  
  throw createError({ statusCode: 405, message: 'Method not allowed' });
});

// This single file works on:
// - Node.js server
// - Cloudflare Workers/Pages
// - Vercel Serverless/Edge
// - Netlify Functions/Edge
// - Deno Deploy
// - AWS Lambda
// - And more...

// Just change the Nitro preset in config!

When to Use What Runtime

DECISION MATRIX:

Use EDGE when:
├─► Request latency is critical (< 50ms)
├─► Doing auth, redirects, header manipulation
├─► A/B testing, personalization
├─► Simple data transformations
└─► Global user base

Use SERVERLESS when:
├─► Database connections needed
├─► Moderate computation
├─► Node.js ecosystem required
├─► Moderate traffic with spikes
└─► Cost optimization for variable load

Use TRADITIONAL when:
├─► WebSocket connections
├─► Long-running processes
├─► Heavy computation (ML, video)
├─► Predictable high traffic
└─► Full Node.js APIs needed

Use HYBRID when:
├─► Different parts have different needs
├─► Global reach with complex backend
├─► Optimizing for both latency and capability
└─► Most production applications!

For Framework Authors: Building Universal Runtime Systems

Implementation Note: The patterns and code examples below represent one proven approach to building universal runtime systems. The adapter pattern shown here is inspired by Nitro/H3 but other approaches exist—SvelteKit uses a similar adapter system, while Remix uses a different deployment abstraction. The direction shown here provides portable patterns based on web standards. Adapt based on which runtimes you need to support and your polyfill strategy.

Creating Runtime Adapters

// NITRO-STYLE ADAPTER PATTERN

class RuntimeAdapter {
  constructor(name, options = {}) {
    this.name = name;
    this.options = options;
  }
  
  // Transform the universal handler for target runtime
  async adapt(handler, buildOutput) {
    throw new Error('Must implement adapt()');
  }
  
  // Generate entry point for runtime
  generateEntry() {
    throw new Error('Must implement generateEntry()');
  }
  
  // Get runtime-specific build config
  getBuildConfig() {
    return {};
  }
}

// Node.js Adapter
class NodeAdapter extends RuntimeAdapter {
  constructor(options = {}) {
    super('node', options);
    this.port = options.port || 3000;
  }
  
  generateEntry() {
    return `
import { createServer } from 'node:http';
import { toNodeHandler } from 'h3';
import { app } from '#internal/app';

const handler = toNodeHandler(app);
const server = createServer(handler);

server.listen(${this.port}, () => {
  console.log(\`Server running on http://localhost:${this.port}\`);
});
    `.trim();
  }
  
  getBuildConfig() {
    return {
      target: 'node',
      external: ['node:*'],
      minify: false,
    };
  }
}

// Cloudflare Workers Adapter
class CloudflareAdapter extends RuntimeAdapter {
  constructor(options = {}) {
    super('cloudflare', options);
  }
  
  generateEntry() {
    return `
import { toWebHandler } from 'h3';
import { app } from '#internal/app';

const handler = toWebHandler(app);

export default {
  async fetch(request, env, ctx) {
    // Inject environment bindings
    globalThis.__env__ = env;
    
    return handler(request, {
      waitUntil: (p) => ctx.waitUntil(p),
      passThroughOnException: () => ctx.passThroughOnException(),
    });
  },
};
    `.trim();
  }
  
  getBuildConfig() {
    return {
      target: 'webworker',
      format: 'esm',
      external: [],
      minify: true,
      define: {
        'process.env.NODE_ENV': '"production"',
      },
    };
  }
}

// Vercel Edge Adapter
class VercelEdgeAdapter extends RuntimeAdapter {
  constructor(options = {}) {
    super('vercel-edge', options);
  }
  
  generateEntry() {
    return `
import { toWebHandler } from 'h3';
import { app } from '#internal/app';

const handler = toWebHandler(app);

export const config = { runtime: 'edge' };

export default function (request) {
  return handler(request);
}
    `.trim();
  }
  
  // Generate vercel.json config
  generateConfig(routes) {
    return {
      version: 3,
      routes: routes.map(r => ({
        src: r.pattern,
        dest: r.handler,
      })),
    };
  }
}

// AWS Lambda Adapter
class LambdaAdapter extends RuntimeAdapter {
  constructor(options = {}) {
    super('lambda', options);
  }
  
  generateEntry() {
    return `
import { toWebHandler } from 'h3';
import { app } from '#internal/app';

const handler = toWebHandler(app);

export async function handler(event, context) {
  // Convert Lambda event to Web Request
  const request = lambdaToRequest(event);
  
  // Handle request
  const response = await handler(request);
  
  // Convert Web Response to Lambda response
  return responseToLambda(response);
}

function lambdaToRequest(event) {
  const url = \`https://\${event.headers.host}\${event.rawPath}\`;
  return new Request(url, {
    method: event.requestContext.http.method,
    headers: event.headers,
    body: event.body,
  });
}

async function responseToLambda(response) {
  return {
    statusCode: response.status,
    headers: Object.fromEntries(response.headers),
    body: await response.text(),
    isBase64Encoded: false,
  };
}
    `.trim();
  }
}

Implementing Polyfill Presets

// UNENV-STYLE POLYFILL SYSTEM

class EnvironmentPreset {
  constructor(name) {
    this.name = name;
    this.polyfills = new Map();
    this.aliases = new Map();
    this.globals = new Map();
    this.external = new Set();
  }
  
  // Register a polyfill for a module
  polyfill(module, implementation) {
    this.polyfills.set(module, implementation);
    return this;
  }
  
  // Alias one module to another
  alias(from, to) {
    this.aliases.set(from, to);
    return this;
  }
  
  // Define global injection
  global(name, value) {
    this.globals.set(name, value);
    return this;
  }
  
  // Mark module as external
  external(module) {
    this.external.add(module);
    return this;
  }
  
  // Generate bundler config
  toBundlerConfig() {
    return {
      alias: Object.fromEntries(this.aliases),
      define: Object.fromEntries(
        [...this.globals].map(([k, v]) => [`globalThis.${k}`, v])
      ),
      external: [...this.external],
    };
  }
}

// Node preset for edge runtimes
const nodePresetForEdge = new EnvironmentPreset('node-edge')
  // Polyfill Node.js built-ins with web-compatible versions
  .polyfill('node:buffer', 'unenv/runtime/node/buffer')
  .polyfill('node:crypto', 'unenv/runtime/node/crypto')
  .polyfill('node:stream', 'unenv/runtime/node/stream')
  .polyfill('node:events', 'unenv/runtime/node/events')
  .polyfill('node:util', 'unenv/runtime/node/util')
  .polyfill('node:path', 'unenv/runtime/node/path')
  .polyfill('node:url', 'unenv/runtime/node/url')
  
  // Stub Node.js modules not available on edge
  .polyfill('node:fs', 'unenv/runtime/mock/empty')
  .polyfill('node:child_process', 'unenv/runtime/mock/empty')
  .polyfill('node:net', 'unenv/runtime/mock/empty')
  .polyfill('node:tls', 'unenv/runtime/mock/empty')
  
  // Global injections
  .global('process', 'unenv/runtime/node/process')
  .global('Buffer', 'unenv/runtime/node/buffer')
  
  // Aliases
  .alias('buffer', 'node:buffer')
  .alias('stream', 'node:stream')
  .alias('events', 'node:events');

// Create polyfill bundle
function createPolyfillBundle(preset) {
  const imports = [];
  const exports = [];
  
  for (const [module, impl] of preset.polyfills) {
    const safeName = module.replace(/[^a-z0-9]/gi, '_');
    imports.push(`import * as ${safeName} from '${impl}';`);
    exports.push(`'${module}': ${safeName},`);
  }
  
  return `
${imports.join('\n')}

export const polyfills = {
  ${exports.join('\n  ')}
};

// Auto-inject polyfills
for (const [name, impl] of Object.entries(polyfills)) {
  globalThis.__modules__ = globalThis.__modules__ || {};
  globalThis.__modules__[name] = impl;
}
  `.trim();
}

Building a Universal Storage Adapter

// UNSTORAGE-STYLE DRIVER SYSTEM

class StorageDriver {
  constructor(options = {}) {
    this.options = options;
  }
  
  // Required methods
  async hasItem(key) { throw new Error('Not implemented'); }
  async getItem(key) { throw new Error('Not implemented'); }
  async setItem(key, value) { throw new Error('Not implemented'); }
  async removeItem(key) { throw new Error('Not implemented'); }
  async getKeys(base) { throw new Error('Not implemented'); }
  async clear() { throw new Error('Not implemented'); }
  
  // Optional methods
  async getMeta(key) { return {}; }
  async setMeta(key, meta) {}
}

// Memory driver (works everywhere)
class MemoryDriver extends StorageDriver {
  constructor(options = {}) {
    super(options);
    this.data = new Map();
    this.meta = new Map();
  }
  
  async hasItem(key) {
    return this.data.has(key);
  }
  
  async getItem(key) {
    return this.data.get(key) ?? null;
  }
  
  async setItem(key, value) {
    this.data.set(key, value);
    this.meta.set(key, { mtime: Date.now() });
  }
  
  async removeItem(key) {
    this.data.delete(key);
    this.meta.delete(key);
  }
  
  async getKeys(base = '') {
    return [...this.data.keys()].filter(k => k.startsWith(base));
  }
  
  async clear() {
    this.data.clear();
    this.meta.clear();
  }
}

// Cloudflare KV driver
class CloudflareKVDriver extends StorageDriver {
  constructor(options) {
    super(options);
    this.binding = options.binding; // KV namespace binding name
  }
  
  getKV() {
    // Access from Cloudflare env
    return globalThis.__env__?.[this.binding];
  }
  
  async hasItem(key) {
    return (await this.getKV().get(key)) !== null;
  }
  
  async getItem(key) {
    const value = await this.getKV().get(key, 'text');
    return value ? JSON.parse(value) : null;
  }
  
  async setItem(key, value) {
    await this.getKV().put(key, JSON.stringify(value), {
      expirationTtl: this.options.ttl,
    });
  }
  
  async removeItem(key) {
    await this.getKV().delete(key);
  }
  
  async getKeys(base = '') {
    const list = await this.getKV().list({ prefix: base });
    return list.keys.map(k => k.name);
  }
}

// Universal storage factory
function createStorage(options = {}) {
  const drivers = new Map();
  
  return {
    // Mount driver at prefix
    mount(prefix, driver) {
      drivers.set(prefix, driver);
    },
    
    // Get driver for key
    getDriver(key) {
      for (const [prefix, driver] of drivers) {
        if (key.startsWith(prefix)) {
          return { driver, key: key.slice(prefix.length) };
        }
      }
      return { driver: drivers.get('') || new MemoryDriver(), key };
    },
    
    // Unified interface
    async getItem(key) {
      const { driver, key: k } = this.getDriver(key);
      return driver.getItem(k);
    },
    
    async setItem(key, value, opts) {
      const { driver, key: k } = this.getDriver(key);
      return driver.setItem(k, value, opts);
    },
    
    async removeItem(key) {
      const { driver, key: k } = this.getDriver(key);
      return driver.removeItem(k);
    },
  };
}

Implementing Cross-Runtime Crypto

// WEB CRYPTO ABSTRACTION (UNCRYPTO-STYLE)

// Universal crypto that works on Node, Deno, Bun, Edge
const crypto = globalThis.crypto || (await import('node:crypto')).webcrypto;

class UniversalCrypto {
  // Random bytes
  static getRandomBytes(length) {
    const bytes = new Uint8Array(length);
    crypto.getRandomValues(bytes);
    return bytes;
  }
  
  // Generate UUID
  static randomUUID() {
    return crypto.randomUUID();
  }
  
  // Hash with SHA-256
  static async sha256(data) {
    const encoder = new TextEncoder();
    const buffer = typeof data === 'string' ? encoder.encode(data) : data;
    const hash = await crypto.subtle.digest('SHA-256', buffer);
    return new Uint8Array(hash);
  }
  
  // Hash to hex string
  static async sha256Hex(data) {
    const hash = await this.sha256(data);
    return Array.from(hash)
      .map(b => b.toString(16).padStart(2, '0'))
      .join('');
  }
  
  // HMAC signing
  static async hmacSign(key, data) {
    const encoder = new TextEncoder();
    const keyMaterial = await crypto.subtle.importKey(
      'raw',
      encoder.encode(key),
      { name: 'HMAC', hash: 'SHA-256' },
      false,
      ['sign']
    );
    
    const signature = await crypto.subtle.sign(
      'HMAC',
      keyMaterial,
      encoder.encode(data)
    );
    
    return new Uint8Array(signature);
  }
  
  // AES-GCM encryption
  static async encrypt(key, data) {
    const encoder = new TextEncoder();
    const iv = this.getRandomBytes(12);
    
    const keyMaterial = await crypto.subtle.importKey(
      'raw',
      await this.sha256(key),
      'AES-GCM',
      false,
      ['encrypt']
    );
    
    const encrypted = await crypto.subtle.encrypt(
      { name: 'AES-GCM', iv },
      keyMaterial,
      encoder.encode(data)
    );
    
    // Combine IV + encrypted data
    const result = new Uint8Array(iv.length + encrypted.byteLength);
    result.set(iv);
    result.set(new Uint8Array(encrypted), iv.length);
    
    return result;
  }
  
  // AES-GCM decryption
  static async decrypt(key, encryptedData) {
    const iv = encryptedData.slice(0, 12);
    const data = encryptedData.slice(12);
    
    const keyMaterial = await crypto.subtle.importKey(
      'raw',
      await this.sha256(key),
      'AES-GCM',
      false,
      ['decrypt']
    );
    
    const decrypted = await crypto.subtle.decrypt(
      { name: 'AES-GCM', iv },
      keyMaterial,
      data
    );
    
    return new TextDecoder().decode(decrypted);
  }
}

// Base64 URL encoding (works everywhere)
function base64UrlEncode(data) {
  const bytes = typeof data === 'string' 
    ? new TextEncoder().encode(data) 
    : data;
  
  const base64 = btoa(String.fromCharCode(...bytes));
  return base64
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=+$/, '');
}

function base64UrlDecode(str) {
  const base64 = str
    .replace(/-/g, '+')
    .replace(/_/g, '/');
  
  const binary = atob(base64);
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i++) {
    bytes[i] = binary.charCodeAt(i);
  }
  
  return bytes;
}

Building Universal Fetch Wrapper

// OFETCH-STYLE UNIVERSAL FETCH

function createFetch(defaults = {}) {
  return async function $fetch(url, options = {}) {
    const config = {
      ...defaults,
      ...options,
      headers: {
        ...defaults.headers,
        ...options.headers,
      },
    };
    
    // Resolve URL
    const resolvedUrl = config.baseURL 
      ? new URL(url, config.baseURL).toString() 
      : url;
    
    // Prepare request
    const fetchOptions = {
      method: config.method || 'GET',
      headers: config.headers,
      signal: config.signal,
    };
    
    // Handle body
    if (config.body !== undefined) {
      if (typeof config.body === 'object' && 
          !(config.body instanceof FormData) &&
          !(config.body instanceof ReadableStream)) {
        fetchOptions.body = JSON.stringify(config.body);
        fetchOptions.headers['Content-Type'] = 'application/json';
      } else {
        fetchOptions.body = config.body;
      }
    }
    
    // Add query params
    if (config.query) {
      const separator = resolvedUrl.includes('?') ? '&' : '?';
      const params = new URLSearchParams(config.query).toString();
      url = resolvedUrl + separator + params;
    }
    
    // Retry logic
    const maxRetries = config.retry ?? 1;
    let lastError;
    
    for (let attempt = 0; attempt <= maxRetries; attempt++) {
      try {
        // Interceptors - request
        if (config.onRequest) {
          await config.onRequest({ request: fetchOptions, options: config });
        }
        
        const response = await fetch(resolvedUrl, fetchOptions);
        
        // Interceptors - response
        if (config.onResponse) {
          await config.onResponse({ response, options: config });
        }
        
        // Handle errors
        if (!response.ok) {
          const error = new FetchError(response.statusText);
          error.response = response;
          error.status = response.status;
          
          if (config.onResponseError) {
            await config.onResponseError({ error, response, options: config });
          }
          
          throw error;
        }
        
        // Parse response
        const contentType = response.headers.get('content-type') || '';
        
        if (config.responseType === 'stream') {
          return response.body;
        }
        
        if (config.responseType === 'blob') {
          return response.blob();
        }
        
        if (config.responseType === 'arrayBuffer') {
          return response.arrayBuffer();
        }
        
        if (contentType.includes('application/json')) {
          return response.json();
        }
        
        return response.text();
        
      } catch (error) {
        lastError = error;
        
        // Don't retry on certain errors
        if (error.status && error.status < 500) {
          throw error;
        }
        
        // Wait before retry
        if (attempt < maxRetries) {
          await new Promise(r => setTimeout(r, 1000 * (attempt + 1)));
        }
      }
    }
    
    throw lastError;
  };
}

class FetchError extends Error {
  constructor(message) {
    super(message);
    this.name = 'FetchError';
  }
}

// Create default instance
const $fetch = createFetch();

// Create with defaults
const api = createFetch({
  baseURL: 'https://api.example.com',
  headers: {
    'Authorization': 'Bearer token',
  },
  retry: 2,
});

Related Skills

Weekly Installs
3
GitHub Stars
31
First Seen
Feb 6, 2026
Installed on
opencode3
claude-code3
github-copilot3
codex3
kimi-cli3
gemini-cli3