or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

compute-resources.mdcontainer-images.mdcore-application-client.mdfunction-decorators-helpers.mdindex.mdinfrastructure-services.mdruntime-utilities.mdscheduling-reliability.mdstorage-data.mdutility-classes.mdweb-api-integration.md

web-api-integration.mddocs/

0

# Web & API Integration

1

2

Modal provides comprehensive web application serving capabilities, enabling deployment of web servers, REST APIs, and integration with popular web frameworks. These decorators transform Modal functions into web endpoints that can handle HTTP requests and serve web applications.

3

4

## Capabilities

5

6

### ASGI Application Support

7

8

Decorator for serving ASGI-compatible applications like FastAPI, Starlette, and modern async web frameworks.

9

10

```python { .api }

11

def asgi_app(func: Callable) -> Callable:

12

"""Decorator to serve ASGI applications (FastAPI, Starlette, etc.)"""

13

```

14

15

#### Usage Examples

16

17

```python

18

import modal

19

from fastapi import FastAPI

20

21

app = modal.App("fastapi-server")

22

web_app = FastAPI()

23

24

@web_app.get("/")

25

def read_root():

26

return {"message": "Hello from Modal FastAPI!"}

27

28

@web_app.get("/items/{item_id}")

29

def read_item(item_id: int, q: str = None):

30

return {"item_id": item_id, "q": q}

31

32

@web_app.post("/process")

33

def process_data(data: dict):

34

# Expensive processing that benefits from Modal's scaling

35

result = expensive_computation(data)

36

return {"result": result}

37

38

@app.function()

39

@modal.asgi_app()

40

def fastapi_app():

41

return web_app

42

43

# The FastAPI app is now served on Modal's infrastructure

44

```

45

46

### WSGI Application Support

47

48

Decorator for serving WSGI-compatible applications like Flask, Django, and traditional web frameworks.

49

50

```python { .api }

51

def wsgi_app(func: Callable) -> Callable:

52

"""Decorator to serve WSGI applications (Flask, Django, etc.)"""

53

```

54

55

#### Usage Examples

56

57

```python

58

import modal

59

from flask import Flask, request, jsonify

60

61

app = modal.App("flask-server")

62

web_app = Flask(__name__)

63

64

@web_app.route("/")

65

def hello():

66

return jsonify({"message": "Hello from Modal Flask!"})

67

68

@web_app.route("/upload", methods=["POST"])

69

def upload_file():

70

if 'file' not in request.files:

71

return jsonify({"error": "No file uploaded"}), 400

72

73

file = request.files['file']

74

# Process file with Modal's compute resources

75

result = process_uploaded_file(file)

76

return jsonify({"processed": result})

77

78

@web_app.route("/predict", methods=["POST"])

79

def make_prediction():

80

data = request.get_json()

81

# Use Modal for ML inference

82

prediction = run_ml_model(data)

83

return jsonify({"prediction": prediction})

84

85

@app.function()

86

@modal.wsgi_app()

87

def flask_app():

88

return web_app

89

90

# The Flask app is now served on Modal's infrastructure

91

```

92

93

### Web Endpoint

94

95

Decorator for creating simple HTTP endpoints without requiring a full web framework.

96

97

```python { .api }

98

def web_endpoint(func: Callable) -> Callable:

99

"""Decorator to create HTTP web endpoints"""

100

```

101

102

#### Usage Examples

103

104

```python

105

import modal

106

import json

107

108

app = modal.App("simple-api")

109

110

@app.function()

111

@modal.web_endpoint(method="GET")

112

def get_status():

113

"""Simple GET endpoint"""

114

return {

115

"status": "healthy",

116

"timestamp": time.time(),

117

"version": "1.0.0"

118

}

119

120

@app.function()

121

@modal.web_endpoint(method="POST")

122

def process_webhook(data=None):

123

"""Handle webhook POST requests"""

124

if data:

125

# Process webhook data

126

result = handle_webhook_data(data)

127

return {"success": True, "processed": result}

128

else:

129

return {"error": "No data provided"}, 400

130

131

@app.function()

132

@modal.web_endpoint(method="GET", path="/metrics")

133

def get_metrics():

134

"""Custom metrics endpoint"""

135

metrics = collect_application_metrics()

136

return {"metrics": metrics}

137

138

# Endpoints are available at:

139

# GET https://your-app.modal.run/get_status

140

# POST https://your-app.modal.run/process_webhook

141

# GET https://your-app.modal.run/metrics

142

```

143

144

### FastAPI Endpoint

145

146

Specialized decorator optimized for FastAPI applications with enhanced integration features.

147

148

```python { .api }

149

def fastapi_endpoint(func: Callable) -> Callable:

150

"""Decorator specifically for FastAPI endpoints with enhanced features"""

151

```

152

153

#### Usage Examples

154

155

```python

156

import modal

157

from fastapi import FastAPI, HTTPException, Depends

158

from pydantic import BaseModel

159

160

app = modal.App("advanced-fastapi")

161

162

class PredictionRequest(BaseModel):

163

text: str

164

model: str = "default"

165

166

class PredictionResponse(BaseModel):

167

prediction: str

168

confidence: float

169

model_used: str

170

171

@app.function()

172

@modal.fastapi_endpoint()

173

def ml_prediction_api():

174

"""Advanced FastAPI endpoint with automatic docs and validation"""

175

fastapi_app = FastAPI(

176

title="ML Prediction API",

177

description="Machine learning predictions powered by Modal",

178

version="1.0.0"

179

)

180

181

@fastapi_app.post("/predict", response_model=PredictionResponse)

182

async def predict(request: PredictionRequest):

183

try:

184

# Load model and make prediction

185

model = load_ml_model(request.model)

186

prediction, confidence = model.predict(request.text)

187

188

return PredictionResponse(

189

prediction=prediction,

190

confidence=confidence,

191

model_used=request.model

192

)

193

except Exception as e:

194

raise HTTPException(status_code=500, detail=str(e))

195

196

@fastapi_app.get("/models")

197

async def list_models():

198

"""List available models"""

199

models = get_available_models()

200

return {"models": models}

201

202

return fastapi_app

203

204

# Automatic OpenAPI docs available at:

205

# https://your-app.modal.run/docs

206

# https://your-app.modal.run/redoc

207

```

208

209

### Web Server

210

211

Decorator for creating custom web servers with full control over the HTTP handling.

212

213

```python { .api }

214

def web_server(func: Callable) -> Callable:

215

"""Decorator to create custom web servers"""

216

```

217

218

#### Usage Examples

219

220

```python

221

import modal

222

from http.server import HTTPServer, BaseHTTPRequestHandler

223

import json

224

225

app = modal.App("custom-server")

226

227

class CustomHandler(BaseHTTPRequestHandler):

228

def do_GET(self):

229

if self.path == "/health":

230

self.send_response(200)

231

self.send_header('Content-type', 'application/json')

232

self.end_headers()

233

response = {"status": "healthy", "server": "custom"}

234

self.wfile.write(json.dumps(response).encode())

235

else:

236

self.send_response(404)

237

self.end_headers()

238

239

def do_POST(self):

240

if self.path == "/process":

241

content_length = int(self.headers['Content-Length'])

242

post_data = self.rfile.read(content_length)

243

244

try:

245

data = json.loads(post_data.decode('utf-8'))

246

result = custom_processing(data)

247

248

self.send_response(200)

249

self.send_header('Content-type', 'application/json')

250

self.end_headers()

251

self.wfile.write(json.dumps({"result": result}).encode())

252

except Exception as e:

253

self.send_response(500)

254

self.end_headers()

255

256

@app.function()

257

@modal.web_server()

258

def custom_web_server():

259

"""Custom HTTP server with full control"""

260

server = HTTPServer(("0.0.0.0", 8000), CustomHandler)

261

server.serve_forever()

262

```

263

264

## Advanced Web Patterns

265

266

### Multi-Framework API Gateway

267

268

```python

269

import modal

270

from fastapi import FastAPI

271

from flask import Flask

272

import asyncio

273

274

app = modal.App("api-gateway")

275

276

# FastAPI for async endpoints

277

fastapi_app = FastAPI()

278

279

@fastapi_app.get("/async-data")

280

async def get_async_data():

281

# Async data processing

282

data = await fetch_external_data()

283

return {"data": data, "type": "async"}

284

285

# Flask for sync endpoints

286

flask_app = Flask(__name__)

287

288

@flask_app.route("/sync-data")

289

def get_sync_data():

290

# Synchronous data processing

291

data = fetch_local_data()

292

return {"data": data, "type": "sync"}

293

294

@app.function()

295

@modal.asgi_app()

296

def async_api():

297

return fastapi_app

298

299

@app.function()

300

@modal.wsgi_app()

301

def sync_api():

302

return flask_app

303

304

# Different apps serve different endpoints

305

# https://your-app-async.modal.run/async-data

306

# https://your-app-sync.modal.run/sync-data

307

```

308

309

### Microservices Architecture

310

311

```python

312

import modal

313

from fastapi import FastAPI

314

315

app = modal.App("microservices")

316

317

# User service

318

user_app = FastAPI(title="User Service")

319

320

@user_app.get("/users/{user_id}")

321

def get_user(user_id: int):

322

return get_user_from_database(user_id)

323

324

@user_app.post("/users")

325

def create_user(user_data: dict):

326

return create_user_in_database(user_data)

327

328

# Order service

329

order_app = FastAPI(title="Order Service")

330

331

@order_app.get("/orders/{order_id}")

332

def get_order(order_id: int):

333

return get_order_from_database(order_id)

334

335

@order_app.post("/orders")

336

def create_order(order_data: dict):

337

# Validate user exists

338

user_id = order_data.get("user_id")

339

user = get_user_from_database(user_id)

340

if not user:

341

raise HTTPException(status_code=404, detail="User not found")

342

343

return create_order_in_database(order_data)

344

345

# Deploy as separate services

346

@app.function()

347

@modal.asgi_app()

348

def user_service():

349

return user_app

350

351

@app.function()

352

@modal.asgi_app()

353

def order_service():

354

return order_app

355

```

356

357

### WebSocket and Real-time Features

358

359

```python

360

import modal

361

from fastapi import FastAPI, WebSocket

362

from typing import List

363

364

app = modal.App("realtime-app")

365

web_app = FastAPI()

366

367

class ConnectionManager:

368

def __init__(self):

369

self.active_connections: List[WebSocket] = []

370

371

async def connect(self, websocket: WebSocket):

372

await websocket.accept()

373

self.active_connections.append(websocket)

374

375

def disconnect(self, websocket: WebSocket):

376

self.active_connections.remove(websocket)

377

378

async def broadcast(self, message: str):

379

for connection in self.active_connections:

380

await connection.send_text(message)

381

382

manager = ConnectionManager()

383

384

@web_app.websocket("/ws")

385

async def websocket_endpoint(websocket: WebSocket):

386

await manager.connect(websocket)

387

try:

388

while True:

389

data = await websocket.receive_text()

390

# Process message and broadcast to all connections

391

processed = process_realtime_data(data)

392

await manager.broadcast(f"Processed: {processed}")

393

except Exception:

394

manager.disconnect(websocket)

395

396

@web_app.post("/broadcast")

397

def broadcast_message(message: dict):

398

"""HTTP endpoint to broadcast messages"""

399

asyncio.create_task(manager.broadcast(json.dumps(message)))

400

return {"status": "broadcasted"}

401

402

@app.function()

403

@modal.asgi_app()

404

def realtime_app():

405

return web_app

406

```

407

408

### File Upload and Processing API

409

410

```python

411

import modal

412

from fastapi import FastAPI, File, UploadFile, HTTPException

413

from typing import List

414

import tempfile

415

import os

416

417

app = modal.App("file-processor")

418

web_app = FastAPI(title="File Processing API")

419

420

@web_app.post("/upload/single")

421

async def upload_single_file(file: UploadFile = File(...)):

422

"""Upload and process a single file"""

423

if not file.filename.endswith(('.jpg', '.png', '.pdf', '.txt')):

424

raise HTTPException(status_code=400, detail="Unsupported file type")

425

426

# Save file temporarily

427

with tempfile.NamedTemporaryFile(delete=False) as tmp_file:

428

content = await file.read()

429

tmp_file.write(content)

430

tmp_path = tmp_file.name

431

432

try:

433

# Process file with Modal compute

434

if file.filename.endswith(('.jpg', '.png')):

435

result = process_image(tmp_path)

436

elif file.filename.endswith('.pdf'):

437

result = extract_pdf_text(tmp_path)

438

else:

439

result = process_text_file(tmp_path)

440

441

return {

442

"filename": file.filename,

443

"size": len(content),

444

"processed": result

445

}

446

finally:

447

os.unlink(tmp_path)

448

449

@web_app.post("/upload/batch")

450

async def upload_multiple_files(files: List[UploadFile] = File(...)):

451

"""Upload and process multiple files"""

452

results = []

453

454

for file in files:

455

try:

456

# Process each file

457

content = await file.read()

458

result = await process_file_async(file.filename, content)

459

results.append({

460

"filename": file.filename,

461

"status": "success",

462

"result": result

463

})

464

except Exception as e:

465

results.append({

466

"filename": file.filename,

467

"status": "error",

468

"error": str(e)

469

})

470

471

return {"processed_files": results}

472

473

@web_app.get("/download/{file_id}")

474

async def download_processed_file(file_id: str):

475

"""Download processed file results"""

476

file_path = get_processed_file_path(file_id)

477

if not os.path.exists(file_path):

478

raise HTTPException(status_code=404, detail="File not found")

479

480

return FileResponse(file_path)

481

482

@app.function()

483

@modal.asgi_app()

484

def file_processing_api():

485

return web_app

486

```

487

488

### Authentication and Middleware Integration

489

490

```python

491

import modal

492

from fastapi import FastAPI, Depends, HTTPException, status

493

from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials

494

from fastapi.middleware.cors import CORSMiddleware

495

import jwt

496

497

app = modal.App("secure-api")

498

web_app = FastAPI(title="Secure API with Authentication")

499

500

# Add CORS middleware

501

web_app.add_middleware(

502

CORSMiddleware,

503

allow_origins=["*"], # Configure appropriately for production

504

allow_credentials=True,

505

allow_methods=["*"],

506

allow_headers=["*"],

507

)

508

509

security = HTTPBearer()

510

511

def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):

512

"""Verify JWT token"""

513

try:

514

payload = jwt.decode(

515

credentials.credentials,

516

os.environ["JWT_SECRET"],

517

algorithms=["HS256"]

518

)

519

return payload

520

except jwt.ExpiredSignatureError:

521

raise HTTPException(

522

status_code=status.HTTP_401_UNAUTHORIZED,

523

detail="Token expired"

524

)

525

except jwt.InvalidTokenError:

526

raise HTTPException(

527

status_code=status.HTTP_401_UNAUTHORIZED,

528

detail="Invalid token"

529

)

530

531

@web_app.post("/login")

532

def login(credentials: dict):

533

"""Authenticate user and return JWT token"""

534

if authenticate_user(credentials["username"], credentials["password"]):

535

token = create_jwt_token(credentials["username"])

536

return {"access_token": token, "token_type": "bearer"}

537

else:

538

raise HTTPException(

539

status_code=status.HTTP_401_UNAUTHORIZED,

540

detail="Invalid credentials"

541

)

542

543

@web_app.get("/protected")

544

def protected_endpoint(current_user: dict = Depends(verify_token)):

545

"""Protected endpoint requiring authentication"""

546

return {

547

"message": f"Hello {current_user['username']}!",

548

"data": get_user_specific_data(current_user['user_id'])

549

}

550

551

@web_app.get("/admin")

552

def admin_endpoint(current_user: dict = Depends(verify_token)):

553

"""Admin-only endpoint"""

554

if not current_user.get("is_admin"):

555

raise HTTPException(

556

status_code=status.HTTP_403_FORBIDDEN,

557

detail="Admin access required"

558

)

559

560

return {"admin_data": get_admin_data()}

561

562

@app.function(secrets=[modal.Secret.from_name("jwt-secret")])

563

@modal.asgi_app()

564

def secure_api():

565

return web_app

566

```