or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

application-routing.mdauthentication.mdcss-styling.mddevelopment-tools.mdform-handling.mdhtml-components.mdhtmx-integration.mdindex.mdjavascript-integration.mdnotifications.mdsvg-components.md

development-tools.mddocs/

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

```