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:
- Create Your First Function - Deep dive into your preferred runtime.
- Integration Patterns - Connect with Telnyx services.