Quick Start

Get your first serverless function deployed to the edge in under 5 minutes using the EdgeAPI CLI.

Install the CLI

Install the EdgeAPI command-line tool globally via npm:

npm install -g @edgeapi/cli

Initialize a Project

Create a new EdgeAPI project with the interactive setup wizard:

edgeapi init my-first-function cd my-first-function

This creates a project directory with:

  • edgeapi.toml — Project configuration (name, regions, environment variables)
  • src/index.ts — Your handler function (TypeScript by default)
  • .gitignore — Git configuration
  • package.json — Dependencies (if JavaScript/TypeScript)

Write a Handler

The handler is a simple async function that receives a request and returns a response:

// src/index.ts export default { async fetch(request: Request) { const url = new URL(request.url); if (url.pathname === '/api/hello') { return new Response( JSON.stringify({ message: 'Hello from the edge', timestamp: new Date().toISOString(), region: request.headers.get('cf-ray') || 'unknown' }), { headers: { 'content-type': 'application/json' } } ); } return new Response('Not found', { status: 404 }); } };

Deploy

Deploy to all 320 regions with a single command:

edgeapi deploy

You should see output like:

$ edgeapi deploy → Building project: my-first-function ✓ Build successful (156 KB) → Deploying to 320 regions... ✓ Deployed to us-west (2.3s) ✓ Deployed to eu-central (2.1s) ✓ Deployed to ap-southeast (2.4s) ... (317 more regions) ✓ All regions ready (18s total) URL: https://my-first-function.edgeapi.date

Test Your Function

Once deployed, test your function:

curl https://my-first-function.edgeapi.date/api/hello

Response:

{ "message": "Hello from the edge", "timestamp": "2026-05-05T14:32:18.902Z", "region": "sfo-west" }

Local Development

Use the edgeapi dev command to run your function locally with hot-reload:

edgeapi dev

Your function runs at http://localhost:8787 with automatic reload on file changes.

Runtimes

EdgeAPI supports multiple runtimes, each optimized for different use cases. All runtimes share the same Web Standard APIs for maximum portability.

TypeScript / JavaScript

The primary runtime for EdgeAPI. Full support for async/await, ES2022 syntax, and Web APIs.

Memory limits: 128 MB to 3 GB | CPU: Shared (up to 4 cores on 3GB tier) | Cold start: ~5ms

export default { async fetch(request: Request, env: any, ctx: ExecutionContext) { const data = await request.json(); const response = await fetch('https://api.example.com/process', { method: 'POST', body: JSON.stringify(data), headers: { 'content-type': 'application/json' } }); return response; } };

Python 3.12

Full CPython runtime with 2,500+ pre-installed packages (NumPy, Pandas, requests, etc.). Ideal for data processing and ML inference.

Memory limits: 256 MB to 3 GB | CPU: Shared | Cold start: ~80ms

import json from datetime import datetime async def on_fetch(request): data = await request.json() result = { 'processed': True, 'timestamp': datetime.now().isoformat(), 'input_size': len(data) } return Response(json.dumps(result), headers={'content-type': 'application/json'})

Rust

Compile Rust to WebAssembly for maximum performance and security. Rust code runs in a sandboxed WASM environment.

Memory limits: 64 MB to 512 MB | CPU: Dedicated core | Cold start: ~2ms

use wasm_bindgen::prelude::*; use web_sys::Request; #[wasm_bindgen] pub async fn fetch(req: Request) -> String { format!("Hello from Rust at {}", now()) }

Go (TinyGo to WASM)

Use Go with TinyGo for WASM compilation. Ideal for concurrent workloads compiled to a small binary.

Memory limits: 128 MB to 256 MB | CPU: Dedicated core | Cold start: ~4ms

package main import ( "net/http" "time" ) func fetch(w http.ResponseWriter, r *http.Request) { w.Header().Set("content-type", "application/json") w.Write([]byte("{\"timestamp\":\"" + time.Now().String() + "\"}")) }

Deno

Modern JavaScript/TypeScript runtime with native TypeScript support, no build step required.

Memory limits: 128 MB to 2 GB | CPU: Shared | Cold start: ~20ms

export default { async fetch(req: Request) { const { pathname } = new URL(req.url); if (pathname === '/health') { return new Response('OK'); } return new Response('Not found', { status: 404 }); } };

Bun

Fast JavaScript runtime with native TypeScript, JSX, and SQLite support. Zero-dependency bundler.

Memory limits: 128 MB to 2 GB | CPU: Shared | Cold start: ~15ms

export default { async fetch(req: Request) { if (req.method === 'POST') { const body = await req.json(); return Response.json({ received: body }); } return new Response('Method not allowed', { status: 405 }); } };

Package Management

JavaScript/TypeScript: Use npm/yarn. Packages are bundled automatically during deployment.

Python: Use requirements.txt. Specify packages in your project root, they are installed during build.

Rust: Use Cargo.toml. Dependencies compiled with your code.

Go: Use go.mod. All deps compiled into the final WASM binary.

CLI Reference

The edgeapi command-line tool provides full project management, deployment, and debugging capabilities.

edgeapi init

Initialize a new project with guided setup:

edgeapi init [project-name] [--runtime ts|py|rust|go|deno|bun]

Creates a new project directory with handler, config, and dependencies. Defaults to TypeScript.

$ edgeapi init my-api ? Project name: my-api ? Runtime: TypeScript ? Regions to deploy: all ✓ Project created at ./my-api

edgeapi deploy

Deploy the current project to production:

edgeapi deploy [--regions us-west,eu-central] [--env production]

Builds, bundles, and deploys your function to specified regions (defaults to all 320).

$ edgeapi deploy → Validating project... → Building (TypeScript)... ✓ Build successful (142 KB) → Compressing... ✓ Compressed to 34 KB → Pushing to 320 regions... ✓ All regions ready (22s)

edgeapi logs

Stream logs from your deployed function in real-time:

edgeapi logs [--tail] [--level error|warn|info|debug] [--region us-west]

Use --tail to stream live logs. Filter by level or region.

$ edgeapi logs --tail [2026-05-05 14:35:22] INFO GET /api/users — 145ms (us-west) [2026-05-05 14:35:24] INFO POST /api/users — 324ms (eu-central) [2026-05-05 14:35:26] ERROR Database timeout — (ap-southeast)

edgeapi dev

Run your function locally with hot-reload and debugging:

edgeapi dev [--port 8787] [--inspect]

Launches a local development server matching production behavior.

$ edgeapi dev → Starting development server... ✓ Running at http://localhost:8787 ✓ Watching for changes...

edgeapi kv

Manage Edge KV namespaces and keys:

edgeapi kv put <namespace> <key> <value> [--ttl 3600] edgeapi kv get <namespace> <key> edgeapi kv delete <namespace> <key> edgeapi kv list <namespace> [--prefix api:]

Examples:

$ edgeapi kv put cache user:123 '{"name":"Alice"}' $ edgeapi kv get cache user:123 {"name":"Alice"} $ edgeapi kv list cache --prefix user: user:123 user:456 user:789

edgeapi secrets

Manage encrypted environment secrets:

edgeapi secrets set API_KEY "sk-..." edgeapi secrets list edgeapi secrets delete API_KEY

Secrets are encrypted and injected at runtime. They are never logged or exposed in deployments.

edgeapi domains

Manage custom domains and SSL certificates:

edgeapi domains add my-app.com edgeapi domains list edgeapi domains remove my-app.com

Management API

The EdgeAPI Management API provides programmatic access to deployments, projects, logs, and regions. Base URL: https://edgeapi.date/api/v1

Authentication

All API requests require bearer token authentication in the Authorization header:

Authorization: Bearer sk_prod_abc123...

Generate API keys in the dashboard at /dashboard/settings/api-keys

List Regions

GET
/api/v1/regions

Returns all available deployment regions.

Response
{ "regions": [ { "id": "us-west", "name": "US West (California)", "datacenter": "LAX1", "latency_ms": 12, "status": "operational" }, { "id": "eu-central", "name": "EU Central (Frankfurt)", "datacenter": "FRA1", "latency_ms": 145, "status": "operational" }, { "id": "ap-southeast", "name": "Asia Pacific (Singapore)", "datacenter": "SIN1", "latency_ms": 78, "status": "operational" } ], "total": 320 }

Create Project

POST
/api/v1/projects

Create a new project. Requires name and runtime.

Request Body
{ "name": "my-api", "runtime": "typescript", "regions": ["us-west", "eu-central", "ap-southeast"], "description": "User authentication API" }
Response (201 Created)
{ "id": "proj_abc123", "name": "my-api", "runtime": "typescript", "created_at": "2026-05-05T14:30:00Z", "url": "https://my-api.edgeapi.date", "regions": ["us-west", "eu-central", "ap-southeast"] }

Create Deployment

POST
/api/v1/deployments

Deploy a function bundle. Accepts base64-encoded handler code.

Request Body
{ "project_id": "proj_abc123", "handler": "base64_encoded_bundle_here", "metadata": { "version": "1.0.0", "environment": "production" } }
Response (202 Accepted)
{ "deployment_id": "dep_xyz789", "project_id": "proj_abc123", "status": "pending", "created_at": "2026-05-05T14:32:00Z", "regions_target": 320, "regions_ready": 0 }

Get Logs

GET
/api/v1/logs/:project_id

Retrieve logs for a project with optional filtering.

Query Parameters
limit=100 # Max results (default 50, max 1000) offset=0 # Pagination offset level=error|warn|info|debug region=us-west # Filter by region since=2026-05-05T14:00:00Z # ISO 8601 timestamp tail=true # Stream logs (WebSocket upgrade)
Response
{ "logs": [ { "timestamp": "2026-05-05T14:35:22.123Z", "level": "info", "region": "us-west", "request_id": "req_abc123", "message": "GET /api/users", "duration_ms": 145 }, { "timestamp": "2026-05-05T14:35:24.456Z", "level": "error", "region": "eu-central", "request_id": "req_def456", "message": "Database connection timeout", "error_code": "ECONNREFUSED" } ], "total": 2, "next_offset": 2 }

Example: cURL

Deploy using the API with cURL:

curl -X POST https://edgeapi.date/api/v1/deployments \ -H "Authorization: Bearer sk_prod_abc123" \ -H "Content-Type: application/json" \ -d '{ "project_id": "proj_abc123", "handler": "ZXhwb3J0IGRlZmF1bHQgeyBhc3luYyBmZXRjaChyZXEpIHsgcmV0dXJuIG5ldyBSZXNwb25zZSgnSGknKTsgfSB9", "metadata": { "version": "1.0.0" } }'

Webhook Events

Receive real-time notifications about deployments and errors:

  • deployment.queued — Deployment received and queued
  • deployment.building — Build started
  • deployment.built — Build completed
  • deployment.ready — All regions ready
  • function.error — Function raised unhandled exception
  • region.offline — Region went offline

Edge KV

Edge KV is a globally distributed key-value store replicated across all 320 regions. It provides low-latency, eventually-consistent storage for application state, caches, and sessions.

Overview

Edge KV operates as follows:

  • Eventual Consistency: Writes to one region propagate to all others within 2-5 seconds
  • 320 Regions: Read from any region with ~<5ms latency
  • TTL Support: Keys automatically expire after configured TTL
  • Atomic Operations: Compare-and-swap (CAS) for distributed consensus
  • Namespaces: Organize data with separate namespaces per project

Basic Operations

Access Edge KV via the env.EDGE_KV binding in your handler:

export default { async fetch(request: Request, env: Env) { const kv = env.EDGE_KV; // Put a key await kv.put('user:123:profile', JSON.stringify({ name: 'Alice', email: 'alice@example.com' }), { expirationTtl: 3600 } ); // Get a key const profile = await kv.get('user:123:profile', 'json'); // Delete a key await kv.delete('user:123:profile'); // List keys with prefix const list = await kv.list({ prefix: 'user:', limit: 100 }); return new Response(JSON.stringify({ list })); } };

Advanced Patterns

Atomic Compare-and-Swap:

// Increment counter atomically const counter = await kv.get('stats:views'); const newValue = ((counter ? parseInt(counter) : 0) + 1).toString(); await kv.put('stats:views', newValue, { version: counter.version });

Session Storage with TTL:

// Store session, auto-expire after 24 hours await kv.put( `session:${sessionId}`, JSON.stringify({ userId: user.id, token: jwt }), { expirationTtl: 86400 } );

Consistency Model

Edge KV provides eventual consistency with these guarantees:

  • Writes are immediately visible in the same region
  • Reads in other regions see the latest value within 2-5 seconds
  • Deleted keys are purged from all regions within 5 seconds
  • For strong consistency, use Edge D1 instead

CLI Commands

# Put a value edgeapi kv put cache user:123 '{"name":"Alice"}' --ttl 3600 # Get a value edgeapi kv get cache user:123 # Delete a key edgeapi kv delete cache user:123 # List keys with prefix edgeapi kv list cache --prefix api:

Edge D1

Edge D1 is a globally distributed SQLite database. Each region maintains a replica of your database, providing low-latency reads and strong consistency within a region.

Architecture

  • SQLite: Full SQL support with familiar syntax
  • Primary + Replicas: One primary region, replicas in all others
  • Writes: Must route to primary (automatic failover to regional replica on primary failure)
  • Reads: Always local in any region (~<1ms)
  • Replication: ~100-500ms lag, tuned for consistency

Schema Migrations

Manage schema with migration files in migrations/:

-- migrations/001_initial.sql CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT UNIQUE NOT NULL, name TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE posts ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, title TEXT NOT NULL, content TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ); CREATE INDEX idx_posts_user_id ON posts(user_id);

Querying from Handlers

export default { async fetch(request: Request, env: Env) { const db = env.EDGE_D1; // Simple query const users = await db.prepare( 'SELECT * FROM users WHERE created_at > ?' ).bind(new Date(Date.now() - 86400000)).all(); // Insert with parameters (prevents SQL injection) const result = await db.prepare( 'INSERT INTO users (email, name) VALUES (?, ?)' ).bind(email, name).run(); // Transaction const tx = db.prepare('BEGIN TRANSACTION').run(); await db.prepare('INSERT INTO posts (user_id, title) VALUES (?, ?)') .bind(userId, title).run(); await db.prepare('UPDATE users SET post_count = post_count + 1 WHERE id = ?') .bind(userId).run(); db.prepare('COMMIT').run(); return new Response(JSON.stringify(users)); } };

Prepared Statements

Always use prepared statements with parameter binding to prevent SQL injection:

// SAFE: Uses parameter binding await db.prepare('SELECT * FROM users WHERE id = ?').bind(id).first(); // UNSAFE: String interpolation // await db.exec(`SELECT * FROM users WHERE id = ${id}`); // DON'T DO THIS

Streaming & WebSockets

EdgeAPI supports HTTP streaming, Server-Sent Events (SSE), and WebSocket connections for real-time communication.

HTTP Streaming

Stream large responses without buffering:

export default { async fetch(request: Request) { const { readable, writable } = new TransformStream(); const writer = writable.getWriter(); // Write data asynchronously (async () => { for (let i = 0; i < 1000000; i++) { await writer.write(new TextEncoder().encode(`${i}\n`)); } await writer.close(); })(); return new Response(readable, { headers: { 'content-type': 'text/plain' } }); } };

Server-Sent Events

Push updates to clients in real-time:

export default { async fetch(request: Request) { if (request.url.endsWith('/events')) { const { readable, writable } = new TransformStream(); const writer = writable.getWriter(); // Send SSE events (() => { writer.write(new TextEncoder().encode( 'data: {\"message\":\"connected\"}\n\n' )); let counter = 0; const interval = setInterval(async () => { await writer.write(new TextEncoder().encode( `data: {"counter":${counter++}}\n\n` )); }, 1000); })(); return new Response(readable, { headers: { 'content-type': 'text/event-stream', 'cache-control': 'no-cache' } }); } } };

WebSockets

Full-duplex WebSocket connections for bidirectional communication:

export default { async fetch(request: Request) { if (request.headers.get('upgrade') === 'websocket') { const { socket, response } = Deno.upgradeWebSocket(request); socket.onopen = () => { socket.send(JSON.stringify({ type: 'connected' })); }; socket.onmessage = (event) => { const msg = JSON.parse(event.data); socket.send(JSON.stringify({ type: 'echo', data: msg })); }; socket.onclose = () => console.log('Connection closed'); socket.onerror = (err) => console.error(err); return response; } } };

Secrets & Environment Variables

Safely manage API keys, database passwords, and other sensitive configuration.

Setting Secrets

Use the CLI to set encrypted secrets:

edgeapi secrets set DATABASE_URL "postgresql://user:pass@localhost/db" edgeapi secrets set API_KEY "sk_prod_abc123..." edgeapi secrets list edgeapi secrets delete API_KEY

Accessing in Code

Access secrets via the env parameter:

export default { async fetch(request: Request, env: Env) { const dbUrl = env.DATABASE_URL; const apiKey = env.API_KEY; const response = await fetch('https://api.example.com/data', { headers: { 'Authorization': `Bearer ${apiKey}` } }); return response; } };

Environment-Specific Config

Use edgeapi.toml for environment-specific values:

[env.production] DATABASE_URL = "postgresql://prod-user:...@prod.db" LOG_LEVEL = "error" [env.staging] DATABASE_URL = "postgresql://stage-user:...@stage.db" LOG_LEVEL = "info"

Deploy to specific environment:

edgeapi deploy --env production

Security Best Practices

  • Never hardcode secrets in code or config files
  • Rotate API keys regularly
  • Use least-privilege API keys (read-only when possible)
  • Audit secret usage via logs
  • Enable secret rotation reminders in dashboard settings

Observability

Monitor, debug, and understand your functions with logs, metrics, and distributed tracing.

Logs

All console.log(), console.error(), and console.warn() calls are automatically captured:

export default { async fetch(request: Request) { console.info(`Received ${request.method} ${request.url}`); try { const result = await processRequest(request); console.log('Processing complete', { duration: 145, status: 'ok' }); return new Response(result); } catch (err) { console.error('Processing failed', { error: err.message }); return new Response('Error', { status: 500 }); } } };

View logs in real-time:

edgeapi logs --tail --level debug

Structured Logging

Log structured data for better filtering and analysis:

console.info(JSON.stringify({ event: 'user_login', user_id: user.id, email: user.email, ip: request.headers.get('cf-connecting-ip'), user_agent: request.headers.get('user-agent'), timestamp: new Date().toISOString() }));

Metrics

EdgeAPI automatically captures these metrics:

  • Request count — Total requests per region, status code
  • Latency — P50, P95, P99 response times
  • Error rate — 4xx and 5xx error percentages
  • Cold starts — Count and duration
  • Memory usage — Peak and average per invocation
  • CPU time — Execution time per region

Access metrics via the dashboard or API:

curl -H "Authorization: Bearer sk_prod_..." \ https://edgeapi.date/api/v1/metrics?project_id=proj_abc123&since=2026-05-05T00:00:00Z

Distributed Tracing (OpenTelemetry)

EdgeAPI supports OpenTelemetry for distributed tracing:

import { trace } from '@opentelemetry/api'; const tracer = trace.getTracer('my-app'); export default { async fetch(request: Request, env: Env) { const span = tracer.startSpan('handle-request'); const dbSpan = tracer.startSpan('query-database', { parent: span }); const data = await env.EDGE_D1.prepare( 'SELECT * FROM users WHERE id = ?' ).bind(userId).first(); dbSpan.end(); span.end(); return new Response(JSON.stringify(data)); } };

Monitoring Dashboard

Access real-time monitoring at /dashboard/projects/:id/monitor:

  • Live request stream with latencies
  • Error rate and error log explorer
  • Latency percentiles (P50, P95, P99, P99.9)
  • Regional heatmap showing latency and error rates
  • Memory and CPU usage trends
  • Cost breakdown by region

Troubleshooting

Common issues and solutions when building with EdgeAPI.

Issue 1: Build Failures

Error: Build failed: Module not found: '@mylib/api'

Solution:

  • Ensure all dependencies are listed in package.json
  • Run npm install locally to verify dependencies resolve
  • Check for circular imports or missing exports
  • Use edgeapi dev to test build locally before deploying

Issue 2: Runtime Errors

Error: ReferenceError: fetch is not defined

Solution:

  • Ensure you're using a supported Web API (fetch, URL, Request, Response)
  • Edge runtimes don't have Node.js built-ins (fs, path, etc.) — use alternatives:
  • For file operations, use Edge KV or Edge D1
  • For HTTP, use the standard Fetch API
  • Review logs with edgeapi logs --tail to see full error traces

Issue 3: Edge KV Consistency

Problem: Value written in us-west doesn't appear immediately in eu-central

Solution:

  • Edge KV provides eventual consistency (2-5 second replication lag)
  • For immediate consistency, read from the region where you wrote
  • For global immediate consistency, use Edge D1 instead
  • Use env.EDGE_KV.put(..., { metadata: { sync: true } }) for critical writes (slightly higher latency)

Issue 4: Deployment Timeout

Error: Deployment timeout after 60s

Solution:

  • Handler functions have a 5-second execution timeout
  • Long-running operations should defer to background workers
  • For large bundle sizes (>100 MB), consider splitting into multiple functions
  • Check deployment logs: edgeapi logs --region deployment-worker

Issue 5: Cold Start Latency

Problem: Some requests take 500ms+ to complete

Solution:

  • EdgeAPI supports warm pools for low cold-start rates (<5%)
  • First invocation in a region is a cold start (~50-200ms depending on runtime)
  • Use edgeapi metrics to measure cold start frequency
  • Consider pre-warming: schedule periodic requests in low-traffic hours
  • Minimize bundle size to reduce cold start penalty

Issue 6: Region Unavailability

Error: us-east region offline

Solution:

  • Check the status dashboard: /status/
  • Enable automatic failover in settings: requests route to nearest available region
  • Exclude specific regions from deployment: edgeapi deploy --regions us-west,eu-central
  • Subscribe to incident notifications for urgent updates