Skip to main content

Edge Compute Fundamentals

Learn the core concepts, function lifecycle, and configuration patterns for building serverless functions on Telnyx Edge Compute.

Function Anatomy

Every Telnyx Edge Compute function consists of three essential components:

Function Code

The main application code that handles HTTP requests and responses:

// JavaScript example
export default async function(request) {
const data = await request.json();

return new Response(JSON.stringify({
message: "Hello from the edge",
received: data
}), {
headers: { "Content-Type": "application/json" }
});
}
// Go example  
func handler(w http.ResponseWriter, r *http.Request) {
response := map[string]string{
"message": "Hello from the edge",
"method": r.Method,
"path": r.URL.Path,
}

w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}

Runtime Dependencies

Language-specific dependency files that define required packages:

// package.json (Node.js)
{
"name": "my-edge-function",
"version": "1.0.0",
"dependencies": {
"@telnyx/sdk": "^2.0.0"
}
}
// go.mod (Go)
module my-edge-function

go 1.21

Function Configuration

The func.toml file that defines deployment and runtime configuration:

[edge_compute]
func_id = "3a62135b-22fb-4902-bd4e-b651761ab777"
func_name = "hello-world"
region = "us-east-1"

[cloud_storage]
bucket_name = "my-bucket"
region = "us-east-1"

[env_vars]
MY_KEY = "VAL"

Function Lifecycle

Understanding the function lifecycle helps you optimize performance and handle different execution phases:

Cold Start Phase

When a function receives its first request or scales up:

// Global initialization (runs once per container)
const database = initializeDB();
const cache = new Map();

// Function handler (runs per request)
export default async function(request) {
// This code runs for every request
const data = await request.json();

// Reuse initialized resources
const result = await database.query('SELECT * FROM users');

return new Response(JSON.stringify(result));
}

Cold Start Optimization Tips:

  • Initialize expensive resources globally (outside the handler).
  • Minimize dependency imports.
  • Use connection pooling for databases.
  • Cache frequently used data.

Warm Execution

Subsequent requests use the same container instance:

// Global variables persist between requests
var (
client *http.Client
logger *log.Logger
)

func init() {
// One-time initialization.
client = &http.Client{Timeout: 10 * time.Second}
logger = log.New(os.Stdout, "[EDGE] ", log.LstdFlags)
}

func handler(w http.ResponseWriter, r *http.Request) {
// Fast execution using pre-initialized resources.
logger.Printf("Request: %s %s", r.Method, r.URL.Path)

// Use cached client.
resp, err := client.Get("https://api.example.com/data")
// Handle response...
}

Container Recycling

Containers are recycled after periods of inactivity or resource constraints.

Request and Response Patterns

HTTP Request Handling

Functions receive standard HTTP requests with full access to headers, body, and query parameters:

import json
from urllib.parse import parse_qs

async def handler(request):
# Access request details
method = request.method
path = request.url.path
headers = dict(request.headers)
query_params = parse_qs(request.url.query)

# Handle different methods
if method == "POST":
body = await request.json()
return {"message": "Data received", "data": body}
elif method == "GET":
return {"message": "Hello from GET", "params": query_params}
else:
return {"error": "Method not allowed"}, 405

Response Patterns

JSON Responses

// Simple JSON response
return new Response(JSON.stringify({
success: true,
data: { id: 123, name: "John" }
}), {
headers: { "Content-Type": "application/json" }
});

HTML Responses

func handler(w http.ResponseWriter, r *http.Request) {
html := `<html><body><h1>Hello from Edge</h1></body></html>`

w.Header().Set("Content-Type", "text/html")
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, html)
}

Binary Responses

use warp::{http::Response, hyper::Body};

async fn handler() -> Result<Response<Body>, warp::Rejection> {
let image_data = include_bytes!("../assets/logo.png");

Ok(Response::builder()
.header("content-type", "image/png")
.body(Body::from(&image_data[..]))
.unwrap())
}

Error Handling

Implement proper error handling and status codes:

export default async function(request) {
try {
const data = await request.json();

if (!data.email) {
return new Response(JSON.stringify({
error: "Email is required"
}), {
status: 400,
headers: { "Content-Type": "application/json" }
});
}

const result = await processEmail(data.email);
return new Response(JSON.stringify(result));

} catch (error) {
console.error("Function error:", error);

return new Response(JSON.stringify({
error: "Internal server error"
}), {
status: 500,
headers: { "Content-Type": "application/json" }
});
}
}

Configuration Management

Environment Variables

Use environment variables for configuration that changes between environments:

# func.toml
[envvars]
DATABASE_URL = "postgres://user:pass@db:5432/prod"
API_KEY = "prod-api-key-123"
DEBUG = "false"
CACHE_TTL = "3600"

Access in your function:

import os

async def handler(request):
db_url = os.environ.get("DATABASE_URL")
api_key = os.environ.get("API_KEY")
debug = os.environ.get("DEBUG", "false").lower() == "true"
cache_ttl = int(os.environ.get("CACHE_TTL", "300"))

if debug:
print(f"Connecting to: {db_url}")

Secrets Management

For sensitive data, use the CLI secrets management:

# Store secrets securely
telnyx-edge secrets add DATABASE_PASSWORD "super-secret-password"
telnyx-edge secrets add API_TOKEN "sensitive-api-token"

# List secrets (values are never shown)
telnyx-edge secrets list

Access secrets in your function like environment variables:

func handler(w http.ResponseWriter, r *http.Request) {
dbPassword := os.Getenv("DATABASE_PASSWORD")
apiToken := os.Getenv("API_TOKEN")

// Use secrets to connect to external services
db := connectDB("user:"+dbPassword+"@host/db")
}

Performance Patterns

Connection Reuse

Reuse connections and expensive resources:

// Initialize once (outside handler)
const dbPool = createConnectionPool({
host: process.env.DATABASE_URL,
maxConnections: 10
});

const httpClient = new HttpClient({
timeout: 10000,
keepAlive: true
});

// Use in handler
export default async function(request) {
// Reuse pooled connection
const db = await dbPool.getConnection();
const result = await db.query('SELECT * FROM users');

// Reuse HTTP client
const response = await httpClient.get('https://api.example.com');

return new Response(JSON.stringify({ result, external: response.data }));
}

Memory Management

Optimize memory usage for better performance:

// Avoid large global variables
var smallCache = make(map[string]string, 100)

func handler(w http.ResponseWriter, r *http.Request) {
// Process data in chunks for large datasets
data := processInChunks(getLargeDataset())

// Clear unused references
defer clearCache()

json.NewEncoder(w).Encode(data)
}

func clearCache() {
if len(smallCache) > 50 {
smallCache = make(map[string]string, 100)
}
}

Async Operations

Use asynchronous patterns for I/O operations:

import asyncio
import aiohttp

async def handler(request):
# Parallel API calls
async with aiohttp.ClientSession() as session:
tasks = [
fetch_user_data(session, "user1"),
fetch_user_data(session, "user2"),
fetch_user_data(session, "user3")
]

results = await asyncio.gather(*tasks)

return {"users": results}

async def fetch_user_data(session, user_id):
async with session.get(f"https://api.users.com/{user_id}") as response:
return await response.json()

Function Naming and Organization

Naming Conventions

Follow consistent naming patterns:

# Good function names
telnyx-edge new-func -l=go -n=webhook-processor
telnyx-edge new-func -l=js -n=user-auth-service
telnyx-edge new-func -l=python -n=data-transformer

# Avoid these patterns
telnyx-edge new-func -l=go -n=myFunction # Wrong case
telnyx-edge new-func -l=js -n=user_service # Underscores not allowed
telnyx-edge new-func -l=python -n=-processor # Cannot start with dash

Naming Rules:

  • Maximum 255 characters.
  • Use lowercase with dashes.
  • Must start and end with alphanumeric characters.
  • No underscores or special characters.

Project Structure

Organize related functions logically:

my-app-functions/
├── api-gateway/ # HTTP API router
├── webhook-handlers/ # Webhook processors
├── data-processors/ # Background jobs
├── auth-services/ # Authentication
└── notification-senders/ # Email/SMS senders

Next Steps

Now that you understand the fundamentals:

  1. Create Your First Function - Deep dive into your preferred runtime.
  2. Integration Patterns - Connect with Telnyx services.