Documentation Index
Fetch the complete documentation index at: https://developers.telnyx.com/llms.txt
Use this file to discover all available pages before exploring further.
Develop and test your Edge Compute functions on your local machine before deploying to Telnyx infrastructure. Run your function code locally using your language’s native tools, then deploy with telnyx-edge ship.
Development Workflow
The local development workflow:
- Create function with
telnyx-edge new-func
- Develop locally using native language tools
- Test with your language’s test framework
- Deploy with
telnyx-edge ship
Quick Start
Create a new function and start developing:
# Create a new function
telnyx-edge new-func -l=python -n=my-function
cd my-function
Running Locally by Language
The scaffolded Go function uses the function package. To test locally, create a test harness:// handler_test.go
package function
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestHandle(t *testing.T) {
req := httptest.NewRequest("GET", "/", nil)
rec := httptest.NewRecorder()
Handle(rec, req)
if rec.Code != 200 {
t.Errorf("Expected status 200, got %d", rec.Code)
}
}
Run tests:For local HTTP testing, you can create a temporary main.go:// +build ignore
package main
import (
"net/http"
"example.com/hello-world/function"
)
func main() {
http.HandleFunc("/", function.Handle)
http.ListenAndServe(":8080", nil)
}
Run with: Python functions use ASGI protocol. To test locally, create an ASGI app wrapper and use uvicorn:# Install uvicorn
pip install uvicorn
# Create an ASGI app wrapper
# In app.py:
# app.py - ASGI wrapper for local testing
from function import new
# new() returns a Function instance, .handle is the ASGI app
func = new()
app = func.handle
# Run with:
uvicorn app:app --port 8080
Or create a simple test script:# test_local.py
import asyncio
import json
from function import Function
async def test_handle():
func = Function()
responses = []
async def receive():
return {"type": "http.request", "body": b""}
async def send(message):
responses.append(message)
scope = {
"type": "http",
"method": "GET",
"path": "/",
"headers": [],
}
await func.handle(scope, receive, send)
print("Response:", responses)
# Should contain response.start and response.body
asyncio.run(test_handle())
Run with: Quarkus functions can be tested using Quarkus test mode:# Run tests
./mvnw test
# Or with Maven
mvn test
Create a test class:// src/test/java/com/telnyx/edge/FunctionTest.java
package com.telnyx.edge;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
@QuarkusTest
public class FunctionTest {
@Test
public void testHello() {
Function func = new Function();
var result = func.hello(null);
assertNotNull(result);
assertEquals("success", result.get("status"));
}
}
Run with:
Environment Variables
During local development, set environment variables in your shell or a .env file:
# Option 1: Export in shell
export LOG_LEVEL=debug
export API_URL=https://api.example.com
# Option 2: Use a .env file
echo 'LOG_LEVEL=debug' >> .env
echo 'API_URL=https://api.example.com' >> .env
// Install godotenv
// go get github.com/joho/godotenv
import (
"os"
"github.com/joho/godotenv"
)
func init() {
godotenv.Load() // Load .env file
}
func getLogLevel() string {
return os.Getenv("LOG_LEVEL")
}
# Install python-dotenv
# pip install python-dotenv
from dotenv import load_dotenv
load_dotenv() # Load .env file
import os
log_level = os.getenv("LOG_LEVEL", "info")
// Quarkus supports .env files automatically
// Or use application.properties
// LOG_LEVEL=debug
import org.eclipse.microprofile.config.inject.ConfigProperty;
import jakarta.inject.Inject;
public class Function {
@ConfigProperty(name = "LOG_LEVEL", defaultValue = "info")
String logLevel;
}
Add .env to your .gitignore to avoid committing local values.
Secrets in Local Development
For local development, mock secrets with environment variables:
# Set secrets locally
export MY_API_KEY="test-key-12345"
export DATABASE_URL="postgres://localhost:5432/devdb"
Your code accesses them the same way it would in production:
import os
api_key = os.getenv("MY_API_KEY")
Never commit actual secret values. Use test/mock values locally and set real secrets with telnyx-edge secrets add for production.
Testing Requests
Once your local server is running, test with curl:
curl http://localhost:8080/
curl -X POST http://localhost:8080/api/data \
-H "Content-Type: application/json" \
-d '{"key": "value"}'
Unit Testing
Write unit tests using your language’s test framework:
// handler_test.go
package function
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestHandleGET(t *testing.T) {
req := httptest.NewRequest("GET", "/", nil)
rec := httptest.NewRecorder()
Handle(rec, req)
if rec.Code != 200 {
t.Errorf("Expected 200, got %d", rec.Code)
}
}
func TestHandlePOST(t *testing.T) {
req := httptest.NewRequest("POST", "/", strings.NewReader(`{"name":"test"}`))
rec := httptest.NewRecorder()
Handle(rec, req)
if rec.Code != 200 && rec.Code != 201 {
t.Errorf("Expected 200 or 201, got %d", rec.Code)
}
}
Run tests:# tests/test_handler.py
import pytest
import asyncio
from function import Function
@pytest.mark.asyncio
async def test_handle_get():
func = Function()
responses = []
async def receive():
return {"type": "http.request", "body": b""}
async def send(message):
responses.append(message)
scope = {"type": "http", "method": "GET", "path": "/", "headers": []}
await func.handle(scope, receive, send)
# Check response.start
assert responses[0]["type"] == "http.response.start"
assert responses[0]["status"] == 200
Run tests:pip install pytest pytest-asyncio
pytest tests/
// src/test/java/com/telnyx/edge/FunctionTest.java
package com.telnyx.edge;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
@QuarkusTest
public class FunctionTest {
@Test
public void testHelloReturnsSuccess() {
Function func = new Function();
Map<String, Object> result = func.hello(Map.of("test", "data"));
assertEquals("success", result.get("status"));
assertNotNull(result.get("message"));
}
}
Run tests:
Differences from Production
Local development differs from production in some ways:
| Feature | Local | Production |
|---|
| Network | localhost | Global edge network |
| Secrets | Environment variables | Encrypted storage |
| Bindings | Mocked/simulated | Connected to services |
| Cold starts | N/A | Container initialization |
| Resource limits | Your machine | Platform limits |
Deploy When Ready
Once your function works locally, deploy to Telnyx:
Your function is now live on the edge network.
Best Practices
Project Structure
my-function/
├── func.toml # Function configuration
├── function/ # Function code (Python)
│ └── __init__.py
├── pyproject.toml # Dependencies (Python)
├── .env # Local env vars (gitignored)
├── .gitignore
└── tests/
└── test_handler.py # Unit tests
Git Ignore
# Local development
.env
.env.local
# Dependencies
__pycache__/
node_modules/
vendor/
# Build artifacts
dist/
build/
*.pyc
target/
Keep Parity
- Use the same language version locally as production
- Match dependency versions
- Test with production-like request payloads