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
# Install dependencies (language-specific)
pip install -r requirements.txt
# Run locally with Python
python app.py
Running Locally by Language
# Install dependencies
npm install
# Run locally with Node.js
node -e "
const { handler } = require('./src/index.js');
const mockRequest = {
method: 'GET',
url: 'http://localhost/test',
headers: new Map(),
json: async () => ({}),
text: async () => ''
};
handler(mockRequest).then(res => console.log(res));
"
Or create a local Express server:// local_server.js
const express = require('express');
const { handler } = require('./src/index.js');
const app = express();
app.use(express.json());
app.all('*', async (req, res) => {
const mockRequest = new Request(`http://localhost${req.url}`, {
method: req.method,
headers: req.headers,
body: req.method !== 'GET' ? JSON.stringify(req.body) : undefined
});
const response = await handler(mockRequest);
// Handle Fetch Response objects
res.status(response.status || 200);
response.headers?.forEach((v, k) => res.set(k, v));
res.send(await response.text());
});
app.listen(8080, () => console.log('Server at http://localhost:8080'));
Run with:# Run locally
go run .
# Or build and run
go build -o function .
./function
Create a local test harness in handler_test.go:// handler_test.go
package main
import (
"net/http/httptest"
"testing"
)
func TestHandler(t *testing.T) {
req := httptest.NewRequest("GET", "/test", nil)
w := httptest.NewRecorder()
handler(w, req)
if w.Code != 200 {
t.Errorf("Expected 200, got %d", w.Code)
}
}
For manual testing, run your function as a local server:# Run all Go files in the package (includes handler)
go run .
Or add a main function to your handler file temporarily for local testing.# Install dependencies
pip install -r requirements.txt
# Run the generated app.py (includes built-in HTTP server)
python app.py
The scaffolded app.py includes a basic HTTP server that starts automatically.
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 with dotenv
echo 'LOG_LEVEL=debug' >> .env
echo 'API_URL=https://api.example.com' >> .env
// Install dotenv
// npm install dotenv
require('dotenv').config();
const logLevel = process.env.LOG_LEVEL || 'info';
// 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")
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:
JavaScript (Jest)
Go
Python (pytest)
// tests/handler.test.js
const { handler } = require('../src/index.js');
const mockRequest = (overrides = {}) => ({
method: 'GET',
url: 'http://localhost/test',
headers: {},
json: async () => ({}),
text: async () => '',
...overrides
});
describe('handler', () => {
test('returns 200 for GET', async () => {
const response = await handler(mockRequest());
expect(response.status).toBe(200);
});
test('handles POST with body', async () => {
const response = await handler(mockRequest({
method: 'POST',
json: async () => ({ name: 'test' })
}));
expect([200, 201]).toContain(response.status);
});
});
Run tests:npm install --save-dev jest
npm test
// handler_test.go
package main
import (
"net/http/httptest"
"strings"
"testing"
)
func TestHandlerGET(t *testing.T) {
req := httptest.NewRequest("GET", "/", nil)
w := httptest.NewRecorder()
handler(w, req)
if w.Code != 200 {
t.Errorf("Expected 200, got %d", w.Code)
}
}
func TestHandlerPOST(t *testing.T) {
req := httptest.NewRequest("POST", "/", strings.NewReader(`{"name":"test"}`))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
handler(w, req)
if w.Code != 200 && w.Code != 201 {
t.Errorf("Expected 200 or 201, got %d", w.Code)
}
}
Run tests:# tests/test_handler.py
import pytest
from app import Handler # Import from scaffolded app.py
def test_handler_exists():
"""Test that Handler class is importable"""
assert Handler is not None
def test_handler_has_do_get():
"""Test that Handler has GET method"""
assert hasattr(Handler, 'do_GET')
Run tests:pip install pytest
pytest tests/
The scaffolded app.py uses Python’s built-in http.server. For async handlers or custom patterns, adapt tests to match your implementation.
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
├── app.py # Function code (Python)
├── .env # Local env vars (gitignored)
├── .gitignore
├── requirements.txt # Dependencies (Python)
└── tests/
└── test_handler.py # Unit tests
Git Ignore
# Local development
.env
.env.local
# Dependencies
__pycache__/
node_modules/
vendor/
# Build artifacts
dist/
build/
*.pyc
Keep Parity
- Use the same language version locally as production
- Match dependency versions
- Test with production-like request payloads