The fastest way to create HTML apps - a next-generation Python web framework for building fast, scalable web applications with minimal code
—
Development server with live reload, Jupyter notebook integration, testing utilities, deployment tools, and development workflow enhancements.
Enhanced development server with hot reload capabilities for rapid development iteration.
class FastHTMLWithLiveReload:
"""
FastHTML app with live reload functionality.
Automatically reloads the browser when source files change,
enabling rapid development workflow.
"""
def __init__(self, app, watch_dirs=None, watch_extensions=None):
"""
Initialize live reload server.
Args:
app: FastHTML application instance
watch_dirs: Directories to watch for changes
watch_extensions: File extensions to monitor
"""Comprehensive Jupyter notebook support for interactive development and prototyping.
def nb_serve(
appname=None,
app='app',
host='127.0.0.1',
port=None,
reload=True,
reload_includes=None,
reload_excludes=None
):
"""
Start Jupyter-compatible server.
Launches development server optimized for Jupyter notebook
workflows with proper port management and reload handling.
Args:
appname: Application module name
app: Application instance or attribute name
host: Host to bind server to
port: Port to bind server to (auto-selected if None)
reload: Enable hot reload
reload_includes: Additional file patterns to watch
reload_excludes: File patterns to exclude from watching
"""
def nb_serve_async(
appname=None,
app='app',
host='127.0.0.1',
port=None,
reload=True
):
"""
Async version of nb_serve for Jupyter environments.
Args:
appname: Application module name
app: Application instance
host: Host to bind to
port: Port to use
reload: Enable reload functionality
Returns:
Async server instance
"""
def show(ft):
"""
Display FastHTML elements in Jupyter notebooks.
Renders FastHTML elements as HTML output in Jupyter
notebook cells for interactive development.
Args:
ft: FastHTML element to display
Returns:
Rendered HTML for Jupyter display
"""
def render_ft(ft) -> str:
"""
Render FastHTML element to HTML string.
Converts FastHTML elements to HTML string format
for display or processing.
Args:
ft: FastHTML element to render
Returns:
str: HTML string representation
"""
def HTMX(path="/", host='localhost', app=None, port=8000, height="auto", link=False, iframe=True):
"""
HTMX app display for Jupyter notebooks.
Sets up HTMX functionality in Jupyter environment with iframe
display, server connectivity, and interactive features.
Args:
path: App path to display
host: Server host address
app: FastHTML application instance
port: Server port number
height: Iframe height
link: Show clickable link
iframe: Display in iframe
Returns:
HTMX configuration and display elements
"""HTTP client for testing FastHTML applications and API endpoints.
class Client:
"""
HTTP client for testing FastHTML apps.
Provides testing interface for FastHTML applications
with support for all HTTP methods and session management.
"""
def __init__(self, app):
"""
Initialize test client.
Args:
app: FastHTML application to test
"""
def get(self, url: str, **kwargs):
"""Send GET request to application."""
def post(self, url: str, data=None, json=None, **kwargs):
"""Send POST request to application."""
def put(self, url: str, data=None, json=None, **kwargs):
"""Send PUT request to application."""
def delete(self, url: str, **kwargs):
"""Send DELETE request to application."""
def patch(self, url: str, data=None, json=None, **kwargs):
"""Send PATCH request to application."""
def head(self, url: str, **kwargs):
"""Send HEAD request to application."""
def options(self, url: str, **kwargs):
"""Send OPTIONS request to application."""
def ws_client(app, nm='', host='localhost', port=8000, ws_connect='/ws', frame=True, link=True, **kwargs):
"""
WebSocket client for Jupyter environments.
Creates WebSocket client for testing and interacting with
WebSocket endpoints in FastHTML applications from Jupyter.
Args:
app: FastHTML application with WebSocket routes
nm: Client name identifier
host: WebSocket server host
port: WebSocket server port
ws_connect: WebSocket connection endpoint
frame: Display in frame
link: Show connection link
**kwargs: Additional client configuration
Returns:
WebSocket client interface
"""Utilities for managing development server ports and availability.
def is_port_free(port: int, host: str = '127.0.0.1') -> bool:
"""
Check if port is available.
Tests whether a specific port is free and available
for binding a development server.
Args:
port: Port number to check
host: Host address to check
Returns:
bool: True if port is available
"""
def wait_port_free(port: int, host: str = '127.0.0.1', max_wait: int = 3):
"""
Wait for port to become free.
Waits for a port to become available, useful for
sequential server startup and testing workflows.
Args:
port: Port number to wait for
host: Host address to check
max_wait: Maximum time to wait in seconds
"""Advanced server management classes for production and development environments.
class JupyUvi:
"""
Jupyter uvicorn server manager.
Manages uvicorn server lifecycle in Jupyter environments
with automatic startup, shutdown, and configuration.
"""
def __init__(self, app, log_level: str = "error", host: str = '0.0.0.0', port: int = 8000, start: bool = True, **kwargs):
"""
Initialize Jupyter uvicorn server.
Args:
app: FastHTML application to serve
log_level: Logging level for server
host: Host address to bind to
port: Port number to bind to
start: Whether to start server immediately
**kwargs: Additional uvicorn configuration
"""
def start(self):
"""Start the uvicorn server."""
def start_async(self):
"""Start server asynchronously."""
def stop(self):
"""Stop the uvicorn server."""
class JupyUviAsync:
"""
Async version of Jupyter uvicorn server manager.
Provides asynchronous server management for integration
with async workflows and event loops.
"""
def __init__(self, app, log_level: str = "error", host: str = '0.0.0.0', port: int = 8000, **kwargs):
"""
Initialize async Jupyter uvicorn server.
Args:
app: FastHTML application to serve
log_level: Logging level for server
host: Host address to bind to
port: Port number to bind to
**kwargs: Additional uvicorn configuration
"""
async def start(self):
"""Start server asynchronously."""
async def stop(self):
"""Stop server asynchronously."""Command-line deployment tools for cloud platforms.
def railway_link() -> str:
"""
Link to Railway deployment.
Generates Railway deployment link for current
FastHTML application.
Returns:
str: Railway deployment URL
"""
def railway_deploy(project_name: str = None):
"""
Deploy to Railway platform.
Deploys FastHTML application to Railway cloud
platform with automatic configuration.
Args:
project_name: Name for Railway project
Returns:
Deployment status and URL
"""from fasthtml.common import *
# Create app with development features
app, rt = fast_app(
debug=True,
live=True, # Enable live reload
reload=True
)
@rt('/')
def homepage():
return Titled("Development Mode",
Div(
H1("FastHTML Development"),
P("This app has live reload enabled."),
P("Changes to this file will automatically refresh the page."),
Button("Test Button", hx_get="/test", hx_target="#result"),
Div(id="result")
)
)
@rt('/test')
def test_endpoint():
from datetime import datetime
return P(f"Test endpoint called at {datetime.now()}")
# Start development server
if __name__ == '__main__':
serve(reload=True, port=8000)# In Jupyter notebook cell
from fasthtml.common import *
# Create app for notebook development
app, rt = fast_app()
@rt('/')
def notebook_demo():
return Div(
H1("Jupyter Integration Demo"),
P("This content is rendered directly in the notebook."),
Form(
Input(type="text", name="message", placeholder="Enter message"),
Button("Submit", hx_post="/echo", hx_target="#output")
),
Div(id="output")
)
@rt('/echo', methods=['POST'])
def echo_message(message: str):
return P(f"You said: {message}", style="color: green;")
# Display in notebook
show(notebook_demo())
# Start server for interactive development
nb_serve(port=8001)from fasthtml.common import *
import pytest
# Create test app
app, rt = fast_app()
@rt('/')
def homepage():
return Div("Welcome to FastHTML")
@rt('/api/users')
def list_users():
return {"users": ["alice", "bob", "charlie"]}
@rt('/api/users', methods=['POST'])
def create_user(name: str, email: str):
# Simulate user creation
user_id = 123
return {"id": user_id, "name": name, "email": email}, 201
@rt('/api/users/{user_id}')
def get_user(user_id: int):
if user_id == 123:
return {"id": 123, "name": "Test User", "email": "test@example.com"}
else:
return {"error": "User not found"}, 404
# Test functions
def test_homepage():
client = Client(app)
response = client.get('/')
assert response.status_code == 200
assert "Welcome to FastHTML" in response.text
def test_list_users():
client = Client(app)
response = client.get('/api/users')
assert response.status_code == 200
data = response.json()
assert "users" in data
assert len(data["users"]) == 3
def test_create_user():
client = Client(app)
response = client.post('/api/users', data={
'name': 'John Doe',
'email': 'john@example.com'
})
assert response.status_code == 201
data = response.json()
assert data["name"] == "John Doe"
assert data["email"] == "john@example.com"
assert "id" in data
def test_get_user_found():
client = Client(app)
response = client.get('/api/users/123')
assert response.status_code == 200
data = response.json()
assert data["id"] == 123
assert data["name"] == "Test User"
def test_get_user_not_found():
client = Client(app)
response = client.get('/api/users/999')
assert response.status_code == 404
data = response.json()
assert "error" in data
# Run tests
if __name__ == '__main__':
test_homepage()
test_list_users()
test_create_user()
test_get_user_found()
test_get_user_not_found()
print("All tests passed!")from fasthtml.common import *
import pytest
import tempfile
import os
# Test app with database
def create_test_app():
# Use temporary database for testing
db_path = tempfile.mktemp(suffix='.db')
app, rt = fast_app(db=db_path)
# Create test routes
@rt('/')
def homepage():
return Titled("Test App", P("Homepage"))
@rt('/users')
def list_users():
users = app.db.users.select().all() if hasattr(app.db, 'users') else []
return {"users": [dict(u) for u in users]}
@rt('/users', methods=['POST'])
def create_user(name: str, email: str):
# Create users table if it doesn't exist
if not hasattr(app.db, 'users'):
app.db.users.create({'name': str, 'email': str}, pk='id')
user_id = app.db.users.insert({'name': name, 'email': email}).last_pk
return {"id": user_id, "name": name, "email": email}
return app, db_path
@pytest.fixture
def app_client():
"""Create app and client for testing."""
app, db_path = create_test_app()
client = Client(app)
yield client
# Cleanup
if os.path.exists(db_path):
os.remove(db_path)
def test_homepage_with_fixture(app_client):
response = app_client.get('/')
assert response.status_code == 200
assert "Test App" in response.text
def test_user_crud_operations(app_client):
# Test empty user list
response = app_client.get('/users')
assert response.status_code == 200
assert response.json()["users"] == []
# Create a user
response = app_client.post('/users', data={
'name': 'Alice Johnson',
'email': 'alice@example.com'
})
assert response.status_code == 200
user_data = response.json()
assert user_data["name"] == "Alice Johnson"
assert user_data["email"] == "alice@example.com"
user_id = user_data["id"]
# Verify user was created
response = app_client.get('/users')
assert response.status_code == 200
users = response.json()["users"]
assert len(users) == 1
assert users[0]["name"] == "Alice Johnson"from fasthtml.common import *
import os
# Enable live reload in development
app, rt = fast_app(
debug=True,
live=True
)
@rt('/')
def development_page():
# Show current file modification time for debugging
current_file = __file__
mod_time = os.path.getmtime(current_file) if os.path.exists(current_file) else 0
return Titled("Live Reload Demo",
Container(
H1("Development with Live Reload"),
P("This page will automatically refresh when you save changes."),
P(f"File last modified: {mod_time}"),
# Development info
Details(
Summary("Development Info"),
Ul(
Li(f"Debug mode: {app.debug}"),
Li(f"Current file: {current_file}"),
Li("Try editing this file and saving - the page will reload!")
)
),
# Test different components
Card(
Header(H3("Live Development")),
P("Add new content here and see it appear immediately."),
Footer(
Button("Test Button", hx_get="/test-reload"),
Div(id="test-output")
)
)
)
)
@rt('/test-reload')
def test_reload():
from datetime import datetime
return P(f"Reloaded at {datetime.now()}", style="color: green;")
# Custom development middleware for debugging
def debug_middleware(request, call_next):
import time
start_time = time.time()
response = call_next(request)
process_time = time.time() - start_time
response.headers["X-Process-Time"] = str(process_time)
return response
# Add middleware in development
if app.debug:
app.middleware('http')(debug_middleware)
# Start with live reload
if __name__ == '__main__':
serve(
reload=True,
reload_includes=['*.py', '*.html', '*.css', '*.js'],
port=8000
)from fasthtml.common import *
import asyncio
app, rt = fast_app()
@rt('/')
def homepage():
return Titled("Port Management Demo",
P("Server running with automatic port selection")
)
def find_free_port(start_port=8000, max_attempts=100):
"""Find a free port starting from start_port."""
for port in range(start_port, start_port + max_attempts):
if is_port_free(port):
return port
raise RuntimeError(f"No free port found in range {start_port}-{start_port + max_attempts}")
def start_development_server():
"""Start server with automatic port selection."""
try:
port = find_free_port(8000)
print(f"Starting server on port {port}")
serve(port=port, reload=True)
except RuntimeError as e:
print(f"Error starting server: {e}")
async def start_async_server():
"""Start server asynchronously."""
port = find_free_port(8000)
# Wait for any existing server to shut down
if not is_port_free(port):
print(f"Waiting for port {port} to become free...")
try:
wait_port_free(port, timeout=10)
except TimeoutError:
port = find_free_port(port + 1)
print(f"Starting async server on port {port}")
server = JupyUviAsync(app, port=port)
await server.start()
return server
if __name__ == '__main__':
# Choose startup method
import sys
if '--async' in sys.argv:
asyncio.run(start_async_server())
else:
start_development_server()from fasthtml.common import *
import os
# Production-ready app configuration
app, rt = fast_app(
debug=False, # Disable debug in production
secret_key=os.getenv('SECRET_KEY', 'fallback-secret-key'),
session_cookie='secure_session',
sess_https_only=True, # HTTPS only in production
pico=True,
htmx=True
)
@rt('/')
def production_homepage():
return Titled("FastHTML Production App",
Container(
H1("Production Ready"),
P("This app is configured for production deployment."),
Card(
Header(H3("Features")),
Ul(
Li("Secure session management"),
Li("HTTPS-only cookies"),
Li("Environment-based configuration"),
Li("Production optimizations")
)
)
)
)
@rt('/health')
def health_check():
"""Health check endpoint for load balancers."""
return {"status": "healthy", "service": "fasthtml-app"}
# Deployment configuration
def get_deployment_config():
return {
'host': os.getenv('HOST', '0.0.0.0'),
'port': int(os.getenv('PORT', 8000)),
'workers': int(os.getenv('WORKERS', 1)),
'reload': os.getenv('RELOAD', 'false').lower() == 'true',
'log_level': os.getenv('LOG_LEVEL', 'info'),
}
if __name__ == '__main__':
config = get_deployment_config()
# For Railway deployment
if os.getenv('RAILWAY_ENVIRONMENT'):
print("Deploying to Railway...")
railway_deploy()
else:
# Local or other deployment
serve(**config)Install with Tessl CLI
npx tessl i tessl/pypi-python-fasthtml