0
# Development Tools
1
2
Development server with live reload, Jupyter notebook integration, testing utilities, deployment tools, and development workflow enhancements.
3
4
## Capabilities
5
6
### Development Server and Live Reload
7
8
Enhanced development server with hot reload capabilities for rapid development iteration.
9
10
```python { .api }
11
class FastHTMLWithLiveReload:
12
"""
13
FastHTML app with live reload functionality.
14
15
Automatically reloads the browser when source files change,
16
enabling rapid development workflow.
17
"""
18
19
def __init__(self, app, watch_dirs=None, watch_extensions=None):
20
"""
21
Initialize live reload server.
22
23
Args:
24
app: FastHTML application instance
25
watch_dirs: Directories to watch for changes
26
watch_extensions: File extensions to monitor
27
"""
28
```
29
30
### Jupyter Notebook Integration
31
32
Comprehensive Jupyter notebook support for interactive development and prototyping.
33
34
```python { .api }
35
def nb_serve(
36
appname=None,
37
app='app',
38
host='127.0.0.1',
39
port=None,
40
reload=True,
41
reload_includes=None,
42
reload_excludes=None
43
):
44
"""
45
Start Jupyter-compatible server.
46
47
Launches development server optimized for Jupyter notebook
48
workflows with proper port management and reload handling.
49
50
Args:
51
appname: Application module name
52
app: Application instance or attribute name
53
host: Host to bind server to
54
port: Port to bind server to (auto-selected if None)
55
reload: Enable hot reload
56
reload_includes: Additional file patterns to watch
57
reload_excludes: File patterns to exclude from watching
58
"""
59
60
def nb_serve_async(
61
appname=None,
62
app='app',
63
host='127.0.0.1',
64
port=None,
65
reload=True
66
):
67
"""
68
Async version of nb_serve for Jupyter environments.
69
70
Args:
71
appname: Application module name
72
app: Application instance
73
host: Host to bind to
74
port: Port to use
75
reload: Enable reload functionality
76
77
Returns:
78
Async server instance
79
"""
80
81
def show(ft):
82
"""
83
Display FastHTML elements in Jupyter notebooks.
84
85
Renders FastHTML elements as HTML output in Jupyter
86
notebook cells for interactive development.
87
88
Args:
89
ft: FastHTML element to display
90
91
Returns:
92
Rendered HTML for Jupyter display
93
"""
94
95
def render_ft(ft) -> str:
96
"""
97
Render FastHTML element to HTML string.
98
99
Converts FastHTML elements to HTML string format
100
for display or processing.
101
102
Args:
103
ft: FastHTML element to render
104
105
Returns:
106
str: HTML string representation
107
"""
108
109
def HTMX(path="/", host='localhost', app=None, port=8000, height="auto", link=False, iframe=True):
110
"""
111
HTMX app display for Jupyter notebooks.
112
113
Sets up HTMX functionality in Jupyter environment with iframe
114
display, server connectivity, and interactive features.
115
116
Args:
117
path: App path to display
118
host: Server host address
119
app: FastHTML application instance
120
port: Server port number
121
height: Iframe height
122
link: Show clickable link
123
iframe: Display in iframe
124
125
Returns:
126
HTMX configuration and display elements
127
"""
128
```
129
130
### Testing and HTTP Client
131
132
HTTP client for testing FastHTML applications and API endpoints.
133
134
```python { .api }
135
class Client:
136
"""
137
HTTP client for testing FastHTML apps.
138
139
Provides testing interface for FastHTML applications
140
with support for all HTTP methods and session management.
141
"""
142
143
def __init__(self, app):
144
"""
145
Initialize test client.
146
147
Args:
148
app: FastHTML application to test
149
"""
150
151
def get(self, url: str, **kwargs):
152
"""Send GET request to application."""
153
154
def post(self, url: str, data=None, json=None, **kwargs):
155
"""Send POST request to application."""
156
157
def put(self, url: str, data=None, json=None, **kwargs):
158
"""Send PUT request to application."""
159
160
def delete(self, url: str, **kwargs):
161
"""Send DELETE request to application."""
162
163
def patch(self, url: str, data=None, json=None, **kwargs):
164
"""Send PATCH request to application."""
165
166
def head(self, url: str, **kwargs):
167
"""Send HEAD request to application."""
168
169
def options(self, url: str, **kwargs):
170
"""Send OPTIONS request to application."""
171
172
def ws_client(app, nm='', host='localhost', port=8000, ws_connect='/ws', frame=True, link=True, **kwargs):
173
"""
174
WebSocket client for Jupyter environments.
175
176
Creates WebSocket client for testing and interacting with
177
WebSocket endpoints in FastHTML applications from Jupyter.
178
179
Args:
180
app: FastHTML application with WebSocket routes
181
nm: Client name identifier
182
host: WebSocket server host
183
port: WebSocket server port
184
ws_connect: WebSocket connection endpoint
185
frame: Display in frame
186
link: Show connection link
187
**kwargs: Additional client configuration
188
189
Returns:
190
WebSocket client interface
191
"""
192
```
193
194
### Port Management Utilities
195
196
Utilities for managing development server ports and availability.
197
198
```python { .api }
199
def is_port_free(port: int, host: str = '127.0.0.1') -> bool:
200
"""
201
Check if port is available.
202
203
Tests whether a specific port is free and available
204
for binding a development server.
205
206
Args:
207
port: Port number to check
208
host: Host address to check
209
210
Returns:
211
bool: True if port is available
212
"""
213
214
def wait_port_free(port: int, host: str = '127.0.0.1', max_wait: int = 3):
215
"""
216
Wait for port to become free.
217
218
Waits for a port to become available, useful for
219
sequential server startup and testing workflows.
220
221
Args:
222
port: Port number to wait for
223
host: Host address to check
224
max_wait: Maximum time to wait in seconds
225
"""
226
```
227
228
### Development Server Management
229
230
Advanced server management classes for production and development environments.
231
232
```python { .api }
233
class JupyUvi:
234
"""
235
Jupyter uvicorn server manager.
236
237
Manages uvicorn server lifecycle in Jupyter environments
238
with automatic startup, shutdown, and configuration.
239
"""
240
241
def __init__(self, app, log_level: str = "error", host: str = '0.0.0.0', port: int = 8000, start: bool = True, **kwargs):
242
"""
243
Initialize Jupyter uvicorn server.
244
245
Args:
246
app: FastHTML application to serve
247
log_level: Logging level for server
248
host: Host address to bind to
249
port: Port number to bind to
250
start: Whether to start server immediately
251
**kwargs: Additional uvicorn configuration
252
"""
253
254
def start(self):
255
"""Start the uvicorn server."""
256
257
def start_async(self):
258
"""Start server asynchronously."""
259
260
def stop(self):
261
"""Stop the uvicorn server."""
262
263
class JupyUviAsync:
264
"""
265
Async version of Jupyter uvicorn server manager.
266
267
Provides asynchronous server management for integration
268
with async workflows and event loops.
269
"""
270
271
def __init__(self, app, log_level: str = "error", host: str = '0.0.0.0', port: int = 8000, **kwargs):
272
"""
273
Initialize async Jupyter uvicorn server.
274
275
Args:
276
app: FastHTML application to serve
277
log_level: Logging level for server
278
host: Host address to bind to
279
port: Port number to bind to
280
**kwargs: Additional uvicorn configuration
281
"""
282
283
async def start(self):
284
"""Start server asynchronously."""
285
286
async def stop(self):
287
"""Stop server asynchronously."""
288
```
289
290
### Deployment Tools
291
292
Command-line deployment tools for cloud platforms.
293
294
```python { .api }
295
def railway_link() -> str:
296
"""
297
Link to Railway deployment.
298
299
Generates Railway deployment link for current
300
FastHTML application.
301
302
Returns:
303
str: Railway deployment URL
304
"""
305
306
def railway_deploy(project_name: str = None):
307
"""
308
Deploy to Railway platform.
309
310
Deploys FastHTML application to Railway cloud
311
platform with automatic configuration.
312
313
Args:
314
project_name: Name for Railway project
315
316
Returns:
317
Deployment status and URL
318
"""
319
```
320
321
## Usage Examples
322
323
### Basic Development Setup
324
325
```python
326
from fasthtml.common import *
327
328
# Create app with development features
329
app, rt = fast_app(
330
debug=True,
331
live=True, # Enable live reload
332
reload=True
333
)
334
335
@rt('/')
336
def homepage():
337
return Titled("Development Mode",
338
Div(
339
H1("FastHTML Development"),
340
P("This app has live reload enabled."),
341
P("Changes to this file will automatically refresh the page."),
342
Button("Test Button", hx_get="/test", hx_target="#result"),
343
Div(id="result")
344
)
345
)
346
347
@rt('/test')
348
def test_endpoint():
349
from datetime import datetime
350
return P(f"Test endpoint called at {datetime.now()}")
351
352
# Start development server
353
if __name__ == '__main__':
354
serve(reload=True, port=8000)
355
```
356
357
### Jupyter Notebook Integration
358
359
```python
360
# In Jupyter notebook cell
361
from fasthtml.common import *
362
363
# Create app for notebook development
364
app, rt = fast_app()
365
366
@rt('/')
367
def notebook_demo():
368
return Div(
369
H1("Jupyter Integration Demo"),
370
P("This content is rendered directly in the notebook."),
371
Form(
372
Input(type="text", name="message", placeholder="Enter message"),
373
Button("Submit", hx_post="/echo", hx_target="#output")
374
),
375
Div(id="output")
376
)
377
378
@rt('/echo', methods=['POST'])
379
def echo_message(message: str):
380
return P(f"You said: {message}", style="color: green;")
381
382
# Display in notebook
383
show(notebook_demo())
384
385
# Start server for interactive development
386
nb_serve(port=8001)
387
```
388
389
### Testing FastHTML Applications
390
391
```python
392
from fasthtml.common import *
393
import pytest
394
395
# Create test app
396
app, rt = fast_app()
397
398
@rt('/')
399
def homepage():
400
return Div("Welcome to FastHTML")
401
402
@rt('/api/users')
403
def list_users():
404
return {"users": ["alice", "bob", "charlie"]}
405
406
@rt('/api/users', methods=['POST'])
407
def create_user(name: str, email: str):
408
# Simulate user creation
409
user_id = 123
410
return {"id": user_id, "name": name, "email": email}, 201
411
412
@rt('/api/users/{user_id}')
413
def get_user(user_id: int):
414
if user_id == 123:
415
return {"id": 123, "name": "Test User", "email": "test@example.com"}
416
else:
417
return {"error": "User not found"}, 404
418
419
# Test functions
420
def test_homepage():
421
client = Client(app)
422
response = client.get('/')
423
424
assert response.status_code == 200
425
assert "Welcome to FastHTML" in response.text
426
427
def test_list_users():
428
client = Client(app)
429
response = client.get('/api/users')
430
431
assert response.status_code == 200
432
data = response.json()
433
assert "users" in data
434
assert len(data["users"]) == 3
435
436
def test_create_user():
437
client = Client(app)
438
response = client.post('/api/users', data={
439
'name': 'John Doe',
440
'email': 'john@example.com'
441
})
442
443
assert response.status_code == 201
444
data = response.json()
445
assert data["name"] == "John Doe"
446
assert data["email"] == "john@example.com"
447
assert "id" in data
448
449
def test_get_user_found():
450
client = Client(app)
451
response = client.get('/api/users/123')
452
453
assert response.status_code == 200
454
data = response.json()
455
assert data["id"] == 123
456
assert data["name"] == "Test User"
457
458
def test_get_user_not_found():
459
client = Client(app)
460
response = client.get('/api/users/999')
461
462
assert response.status_code == 404
463
data = response.json()
464
assert "error" in data
465
466
# Run tests
467
if __name__ == '__main__':
468
test_homepage()
469
test_list_users()
470
test_create_user()
471
test_get_user_found()
472
test_get_user_not_found()
473
print("All tests passed!")
474
```
475
476
### Advanced Testing with Fixtures
477
478
```python
479
from fasthtml.common import *
480
import pytest
481
import tempfile
482
import os
483
484
# Test app with database
485
def create_test_app():
486
# Use temporary database for testing
487
db_path = tempfile.mktemp(suffix='.db')
488
app, rt = fast_app(db=db_path)
489
490
# Create test routes
491
@rt('/')
492
def homepage():
493
return Titled("Test App", P("Homepage"))
494
495
@rt('/users')
496
def list_users():
497
users = app.db.users.select().all() if hasattr(app.db, 'users') else []
498
return {"users": [dict(u) for u in users]}
499
500
@rt('/users', methods=['POST'])
501
def create_user(name: str, email: str):
502
# Create users table if it doesn't exist
503
if not hasattr(app.db, 'users'):
504
app.db.users.create({'name': str, 'email': str}, pk='id')
505
506
user_id = app.db.users.insert({'name': name, 'email': email}).last_pk
507
return {"id": user_id, "name": name, "email": email}
508
509
return app, db_path
510
511
@pytest.fixture
512
def app_client():
513
"""Create app and client for testing."""
514
app, db_path = create_test_app()
515
client = Client(app)
516
517
yield client
518
519
# Cleanup
520
if os.path.exists(db_path):
521
os.remove(db_path)
522
523
def test_homepage_with_fixture(app_client):
524
response = app_client.get('/')
525
assert response.status_code == 200
526
assert "Test App" in response.text
527
528
def test_user_crud_operations(app_client):
529
# Test empty user list
530
response = app_client.get('/users')
531
assert response.status_code == 200
532
assert response.json()["users"] == []
533
534
# Create a user
535
response = app_client.post('/users', data={
536
'name': 'Alice Johnson',
537
'email': 'alice@example.com'
538
})
539
assert response.status_code == 200
540
user_data = response.json()
541
assert user_data["name"] == "Alice Johnson"
542
assert user_data["email"] == "alice@example.com"
543
user_id = user_data["id"]
544
545
# Verify user was created
546
response = app_client.get('/users')
547
assert response.status_code == 200
548
users = response.json()["users"]
549
assert len(users) == 1
550
assert users[0]["name"] == "Alice Johnson"
551
```
552
553
### Development with Live Reload
554
555
```python
556
from fasthtml.common import *
557
import os
558
559
# Enable live reload in development
560
app, rt = fast_app(
561
debug=True,
562
live=True
563
)
564
565
@rt('/')
566
def development_page():
567
# Show current file modification time for debugging
568
current_file = __file__
569
mod_time = os.path.getmtime(current_file) if os.path.exists(current_file) else 0
570
571
return Titled("Live Reload Demo",
572
Container(
573
H1("Development with Live Reload"),
574
P("This page will automatically refresh when you save changes."),
575
P(f"File last modified: {mod_time}"),
576
577
# Development info
578
Details(
579
Summary("Development Info"),
580
Ul(
581
Li(f"Debug mode: {app.debug}"),
582
Li(f"Current file: {current_file}"),
583
Li("Try editing this file and saving - the page will reload!")
584
)
585
),
586
587
# Test different components
588
Card(
589
Header(H3("Live Development")),
590
P("Add new content here and see it appear immediately."),
591
Footer(
592
Button("Test Button", hx_get="/test-reload"),
593
Div(id="test-output")
594
)
595
)
596
)
597
)
598
599
@rt('/test-reload')
600
def test_reload():
601
from datetime import datetime
602
return P(f"Reloaded at {datetime.now()}", style="color: green;")
603
604
# Custom development middleware for debugging
605
def debug_middleware(request, call_next):
606
import time
607
start_time = time.time()
608
609
response = call_next(request)
610
611
process_time = time.time() - start_time
612
response.headers["X-Process-Time"] = str(process_time)
613
614
return response
615
616
# Add middleware in development
617
if app.debug:
618
app.middleware('http')(debug_middleware)
619
620
# Start with live reload
621
if __name__ == '__main__':
622
serve(
623
reload=True,
624
reload_includes=['*.py', '*.html', '*.css', '*.js'],
625
port=8000
626
)
627
```
628
629
### Port Management and Server Control
630
631
```python
632
from fasthtml.common import *
633
import asyncio
634
635
app, rt = fast_app()
636
637
@rt('/')
638
def homepage():
639
return Titled("Port Management Demo",
640
P("Server running with automatic port selection")
641
)
642
643
def find_free_port(start_port=8000, max_attempts=100):
644
"""Find a free port starting from start_port."""
645
for port in range(start_port, start_port + max_attempts):
646
if is_port_free(port):
647
return port
648
raise RuntimeError(f"No free port found in range {start_port}-{start_port + max_attempts}")
649
650
def start_development_server():
651
"""Start server with automatic port selection."""
652
try:
653
port = find_free_port(8000)
654
print(f"Starting server on port {port}")
655
serve(port=port, reload=True)
656
except RuntimeError as e:
657
print(f"Error starting server: {e}")
658
659
async def start_async_server():
660
"""Start server asynchronously."""
661
port = find_free_port(8000)
662
663
# Wait for any existing server to shut down
664
if not is_port_free(port):
665
print(f"Waiting for port {port} to become free...")
666
try:
667
wait_port_free(port, timeout=10)
668
except TimeoutError:
669
port = find_free_port(port + 1)
670
671
print(f"Starting async server on port {port}")
672
server = JupyUviAsync(app, port=port)
673
await server.start()
674
return server
675
676
if __name__ == '__main__':
677
# Choose startup method
678
import sys
679
if '--async' in sys.argv:
680
asyncio.run(start_async_server())
681
else:
682
start_development_server()
683
```
684
685
### Deployment Preparation
686
687
```python
688
from fasthtml.common import *
689
import os
690
691
# Production-ready app configuration
692
app, rt = fast_app(
693
debug=False, # Disable debug in production
694
secret_key=os.getenv('SECRET_KEY', 'fallback-secret-key'),
695
session_cookie='secure_session',
696
sess_https_only=True, # HTTPS only in production
697
pico=True,
698
htmx=True
699
)
700
701
@rt('/')
702
def production_homepage():
703
return Titled("FastHTML Production App",
704
Container(
705
H1("Production Ready"),
706
P("This app is configured for production deployment."),
707
Card(
708
Header(H3("Features")),
709
Ul(
710
Li("Secure session management"),
711
Li("HTTPS-only cookies"),
712
Li("Environment-based configuration"),
713
Li("Production optimizations")
714
)
715
)
716
)
717
)
718
719
@rt('/health')
720
def health_check():
721
"""Health check endpoint for load balancers."""
722
return {"status": "healthy", "service": "fasthtml-app"}
723
724
# Deployment configuration
725
def get_deployment_config():
726
return {
727
'host': os.getenv('HOST', '0.0.0.0'),
728
'port': int(os.getenv('PORT', 8000)),
729
'workers': int(os.getenv('WORKERS', 1)),
730
'reload': os.getenv('RELOAD', 'false').lower() == 'true',
731
'log_level': os.getenv('LOG_LEVEL', 'info'),
732
}
733
734
if __name__ == '__main__':
735
config = get_deployment_config()
736
737
# For Railway deployment
738
if os.getenv('RAILWAY_ENVIRONMENT'):
739
print("Deploying to Railway...")
740
railway_deploy()
741
else:
742
# Local or other deployment
743
serve(**config)
744
```